<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  
  <title>Hexo</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="description">
<meta property="og:type" content="website">
<meta property="og:title" content="Hexo">
<meta property="og:url" content="http://yoursite.com/page/8/index.html">
<meta property="og:site_name" content="Hexo">
<meta property="og:description">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Hexo">
<meta name="twitter:description">
  
    <link rel="alternative" href="/atom.xml" title="Hexo" type="application/atom+xml">
  
  
    <link rel="icon" href="/favicon.png">
  
  <link href="//fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css">
  <link rel="stylesheet" href="/css/style.css" type="text/css">
  

</head>
<body>
  <div id="container">
    <div id="wrap">
      <header id="header">
  <div id="banner"></div>
  <div id="header-outer" class="outer">
    <div id="header-title" class="inner">
      <h1 id="logo-wrap">
        <a href="/" id="logo">Hexo</a>
      </h1>
      
    </div>
    <div id="header-inner" class="inner">
      <nav id="main-nav">
        <a id="main-nav-toggle" class="nav-icon"></a>
        
          <a class="main-nav-link" href="/">Home</a>
        
          <a class="main-nav-link" href="/archives">Archives</a>
        
      </nav>
      <nav id="sub-nav">
        
          <a id="nav-rss-link" class="nav-icon" href="/atom.xml" title="RSS Feed"></a>
        
        <a id="nav-search-btn" class="nav-icon" title="Search"></a>
      </nav>
      <div id="search-form-wrap">
        <form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" results="0" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit">&#xF002;</button><input type="hidden" name="sitesearch" value="http://yoursite.com"></form>
      </div>
    </div>
  </div>
</header>
      <div class="outer">
        <section id="main">
  
    <article id="post-nio/5.SocketChannel文件拷贝" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2015/12/23/nio/5.SocketChannel文件拷贝/" class="article-date">
  <time datetime="2015-12-22T21:00:00.000Z" itemprop="datePublished">2015-12-23</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/nio/">nio</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2015/12/23/nio/5.SocketChannel文件拷贝/">SocketChannel文件拷贝</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="u7B80_u4ECB"><a href="#u7B80_u4ECB" class="headerlink" title="简介"></a>简介</h2><p>　　上一篇我们介绍了FileChannel进行文件拷贝的方法和优势，现在我们将结合FileChannel和SocketChannel进行网络级别的文件拷贝工作。下面示例的功能为服务端收到客户端发送的文件后，再给客户端发送一个文件。</p>
<h2 id="u670D_u52A1_u7AEF"><a href="#u670D_u52A1_u7AEF" class="headerlink" title="服务端"></a>服务端</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyServer</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        Selector selector = <span class="keyword">null</span>;</span><br><span class="line">        ServerSocketChannel serverSocketChannel = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            selector = Selector.open();</span><br><span class="line"></span><br><span class="line">            serverSocketChannel = ServerSocketChannel.open();</span><br><span class="line">            serverSocketChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line"></span><br><span class="line">            serverSocketChannel.socket().setReuseAddress(<span class="keyword">true</span>);</span><br><span class="line">            serverSocketChannel.socket().bind(<span class="keyword">new</span> InetSocketAddress(<span class="number">10000</span>));</span><br><span class="line">            <span class="comment">//我们将ServerSocketChannel注册到了Selector中</span></span><br><span class="line">            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line"></span><br><span class="line">            <span class="comment">//select为一个阻塞方法，如要返回，需要满足以下任意一个条件</span></span><br><span class="line">            <span class="comment">// 1. 注册的某个channel状态与intersets一致了</span></span><br><span class="line">            <span class="comment">// 2. Selector的wakeup方法被调用了</span></span><br><span class="line">            <span class="comment">// 3. 当前的线程被中断了</span></span><br><span class="line">            <span class="comment">//服务端启动后，一直阻塞，直到有客户端建立连接，此时返回1</span></span><br><span class="line">            <span class="comment">//每次有新的客户端连接，都会影响select的返回值</span></span><br><span class="line">            <span class="keyword">while</span> (selector.select() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">//客户端连接，导致ServerSocketChannel的状态为Accept，返回</span></span><br><span class="line">                Iterator&lt;SelectionKey&gt; it = selector.selectedKeys().iterator();</span><br><span class="line">                <span class="keyword">while</span> (it.hasNext()) &#123;</span><br><span class="line">                    SelectionKey readyKey = it.next();</span><br><span class="line">                    it.remove();</span><br><span class="line">                    <span class="comment">//故此处可以强转为ServerSocketChannel</span></span><br><span class="line">                    copy((ServerSocketChannel) readyKey.channel());</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">            ex.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                selector.close();</span><br><span class="line">                serverSocketChannel.close();</span><br><span class="line">            &#125; <span class="keyword">catch</span>(Exception ex) &#123;&#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">copy</span><span class="params">(<span class="keyword">final</span> ServerSocketChannel serverSocketChannel)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        SocketChannel socketChannel = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//accept为每个新客户端的连接创建一个SocketChannel</span></span><br><span class="line">            <span class="comment">//以后服务端采用该SocketChannel与该客户端通信</span></span><br><span class="line">            socketChannel = serverSocketChannel.accept();</span><br><span class="line"></span><br><span class="line">            <span class="comment">//获取客户端发送的文件，保存到c:/server_receive.png</span></span><br><span class="line">            receiveFile(socketChannel, <span class="keyword">new</span> File(<span class="string">"c:/server_receive.png"</span>));</span><br><span class="line"></span><br><span class="line">            <span class="comment">//发送文件c:/server_send.png到客户端</span></span><br><span class="line">            sendFile(socketChannel, <span class="keyword">new</span> File(<span class="string">"c:/server_send.png"</span>));</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                socketChannel.close();</span><br><span class="line">            &#125; <span class="keyword">catch</span>(Exception ex) &#123;&#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">receiveFile</span><span class="params">(SocketChannel socketChannel, File file)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        FileChannel fileWriteChannel = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            fileWriteChannel = <span class="keyword">new</span> FileOutputStream(file).getChannel();</span><br><span class="line">            <span class="comment">//buffer为写模式</span></span><br><span class="line">            ByteBuffer buffer = ByteBuffer.allocateDirect(<span class="number">1024</span>);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">int</span> size = <span class="number">0</span>;</span><br><span class="line">            <span class="comment">//socketChannel读取数据，写到buffer中</span></span><br><span class="line">            <span class="keyword">while</span> ((size = socketChannel.read(buffer)) != -<span class="number">1</span>) &#123;</span><br><span class="line">                <span class="comment">//buffer切换到读模式</span></span><br><span class="line">                buffer.flip();</span><br><span class="line">                <span class="keyword">if</span> (size &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                    buffer.limit(size);</span><br><span class="line">                    fileWriteChannel.write(buffer);</span><br><span class="line">                    <span class="comment">//buffer切换到写模式</span></span><br><span class="line">                    buffer.clear();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123; fileWriteChannel.close();&#125; <span class="keyword">catch</span>(Exception ex) &#123;&#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">sendFile</span><span class="params">(SocketChannel socketChannel, File file)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        FileChannel channel = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            channel = <span class="keyword">new</span> FileInputStream(file).getChannel();</span><br><span class="line">            ByteBuffer buffer = ByteBuffer.allocateDirect(<span class="number">1024</span>);</span><br><span class="line">            <span class="keyword">int</span> size = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">while</span> ((size = channel.read(buffer)) != -<span class="number">1</span>) &#123;</span><br><span class="line">                <span class="comment">//rewind和limit可以合并为flip方法</span></span><br><span class="line">                buffer.rewind();</span><br><span class="line">                buffer.limit(size);</span><br><span class="line">                socketChannel.write(buffer);</span><br><span class="line">                buffer.clear();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//直接优化为下面的方式</span></span><br><span class="line">            <span class="comment">//channel.transferTo(0, channel.size(), socketChannel);</span></span><br><span class="line">            socketChannel.socket().shutdownOutput();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;channel.close();&#125; <span class="keyword">catch</span>(Exception ex) &#123;&#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="u5BA2_u6237_u7AEF"><a href="#u5BA2_u6237_u7AEF" class="headerlink" title="客户端"></a>客户端</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyClient</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">new</span> Thread(<span class="keyword">new</span> MyRunnable()).start();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">MyRunnable</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>&#123;</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            SocketChannel socketChannel = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                socketChannel = SocketChannel.open();</span><br><span class="line">                SocketAddress socketAddress = <span class="keyword">new</span> InetSocketAddress(<span class="string">"localhost"</span>, <span class="number">10000</span>);</span><br><span class="line">                socketChannel.connect(socketAddress);</span><br><span class="line"></span><br><span class="line">                sendFile(socketChannel, <span class="keyword">new</span> File(<span class="string">"c:/client_send.png"</span>));</span><br><span class="line">                receiveFile(socketChannel, <span class="keyword">new</span> File(<span class="string">"c:/client_receive.png"</span>));</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">                ex.printStackTrace();</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;socketChannel.close();&#125; <span class="keyword">catch</span>(Exception ex) &#123;&#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">sendFile</span><span class="params">(SocketChannel socketChannel, File file)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">            FileChannel fileInChannel = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                fileInChannel = <span class="keyword">new</span> FileInputStream(file).getChannel();</span><br><span class="line">                ByteBuffer buffer = ByteBuffer.allocateDirect(<span class="number">1024</span>);</span><br><span class="line">                <span class="keyword">int</span> size = <span class="number">0</span>;</span><br><span class="line">                <span class="keyword">while</span> ((size = fileInChannel.read(buffer)) != -<span class="number">1</span>) &#123;</span><br><span class="line">                    <span class="comment">//两个方法保证了buffer由写模式切换到读模式</span></span><br><span class="line">                    buffer.rewind();</span><br><span class="line">                    buffer.limit(size);</span><br><span class="line">                    socketChannel.write(buffer);</span><br><span class="line">                    buffer.clear();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">//上面代码可以优化为：fileInChannel.transferTo()</span></span><br><span class="line">                <span class="comment">//shutdownOutput用于关闭socket的输出流，对于一个tcp协议</span></span><br><span class="line">                <span class="comment">//之前写入的数据会被发送出去，接着发送tcp正常的连接终止信息</span></span><br><span class="line">                <span class="comment">//调用该方法后，如果仍然向socket写数据，则会抛出io异常</span></span><br><span class="line">                socketChannel.socket().shutdownOutput();</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    fileInChannel.close();</span><br><span class="line">                &#125; <span class="keyword">catch</span>(Exception ex) &#123;&#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">receiveFile</span><span class="params">(SocketChannel socketChannel, File file)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">            FileChannel fileOutChannel = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                fileOutChannel = <span class="keyword">new</span> FileOutputStream(file).getChannel();</span><br><span class="line">                ByteBuffer buffer = ByteBuffer.allocateDirect(<span class="number">1024</span>);</span><br><span class="line"></span><br><span class="line">                <span class="keyword">int</span> size = <span class="number">0</span>;</span><br><span class="line">                <span class="keyword">while</span> ((size = socketChannel.read(buffer)) != -<span class="number">1</span>) &#123;</span><br><span class="line">                    buffer.flip();</span><br><span class="line">                    <span class="keyword">if</span> (size &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                        buffer.limit(size);</span><br><span class="line">                        fileOutChannel.write(buffer);</span><br><span class="line">                        buffer.clear();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    fileOutChannel.close();</span><br><span class="line">                &#125; <span class="keyword">catch</span>(Exception ex) &#123;&#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="u603B_u7ED3"><a href="#u603B_u7ED3" class="headerlink" title="总结"></a>总结</h2><p>　　1. ServerSocketChannel只负责监听到客户端的连接<br>　　2. 在服务端，每次有客户端连接进来，则创建一个对应的SocketChannel，服务端采用该SocketChannel与该客户端通信<br>　　3. 针对不同客户端，服务端都会创建不同的SocketChannel，每个SocketChannel负责与各自的客户端连接通信<br>　　4. 该示例中，Selector只注册了一个ServerSocketChannel，创建的SocketChannel并未注册进去，这里未采用经典的reactor模式的。<br>　　5. 每次有新客户端的连接，都会导致ServerSocketChannel的interests变成OP_ACCEPT，与其注册到Selector时的interests匹配<br>源码下载地址：<a href="/code/socketchannel-copy.rar">socketchannel-copy.rar</a></p>
<h2 id="u53C2_u8003_u5730_u5740"><a href="#u53C2_u8003_u5730_u5740" class="headerlink" title="参考地址"></a>参考地址</h2><p><a href="http://blog.csdn.net/kongxx/article/details/7319410" target="_blank" rel="external">http://blog.csdn.net/kongxx/article/details/7319410</a></p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2015/12/23/nio/5.SocketChannel文件拷贝/" data-id="cijzzpv6q004w1cfsn7hwv3aa" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/java/">java</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/socket/">socket</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-nio/4.FileChannel文件拷贝" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2015/12/23/nio/4.FileChannel文件拷贝/" class="article-date">
  <time datetime="2015-12-22T20:00:00.000Z" itemprop="datePublished">2015-12-23</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/nio/">nio</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2015/12/23/nio/4.FileChannel文件拷贝/">FileChannel文件拷贝</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="u7B80_u4ECB"><a href="#u7B80_u4ECB" class="headerlink" title="简介"></a>简介</h2><p>　　Java NIO中的FileChannel是一个连接到文件的通道，我们可以通过该文件通道读写文件。它无法设置为非阻塞模式，只能运行在阻塞模式下。下面我们通过一个文件拷贝示例解读FileChannel。</p>
<h2 id="u5E38_u7528_u65B9_u6CD5"><a href="#u5E38_u7528_u65B9_u6CD5" class="headerlink" title="常用方法"></a>常用方法</h2><h3 id="position"><a href="#position" class="headerlink" title="position"></a>position</h3><p>　　有时可能需要在FileChannel的某个特定位置进行数据的读/写操作。可以通过调用position()方法获取FileChannel的当前位置。也可以通过调用position(long pos)方法设置FileChannel的当前位置。这里有两个例子:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">long</span> pos = channel.position();</span><br><span class="line">channel.position(pos +<span class="number">123</span>);</span><br></pre></td></tr></table></figure></p>
<p>　　如果将位置设置在文件结束符之后，然后试图从文件通道中读取数据，读方法将返回-1 —— 文件结束标志。<br>　　如果将位置设置在文件结束符之后，然后向通道中写数据，文件将撑大到当前位置并写入数据。这可能导致“文件空洞”，磁盘上物理文件中写入的数据间有空隙。</p>
<h3 id="size"><a href="#size" class="headerlink" title="size"></a>size</h3><p>　　FileChannel的size()方法将返回该实例所关联文件的大小。如:long fileSize = channel.size();</p>
<h3 id="truncate_uFF08_u622A_u77ED_uFF09"><a href="#truncate_uFF08_u622A_u77ED_uFF09" class="headerlink" title="truncate（截短）"></a>truncate（截短）</h3><p>　　可以使用FileChannel.truncate()方法截取一个文件。截取文件时，文件中指定长度后面的部分将被删除。如：channel.truncate(1024);这个例子截取文件的前1024个字节。</p>
<h3 id="force"><a href="#force" class="headerlink" title="force"></a>force</h3><p>　　FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑，操作系统会将数据缓存在内存中，所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点，需要调用force()方法。force()方法有一个boolean类型的参数，指明是否同时将文件元数据（权限信息等）写到磁盘上。</p>
<h3 id="transferFrom_transferTo"><a href="#transferFrom_transferTo" class="headerlink" title="transferFrom transferTo"></a>transferFrom transferTo</h3><p>　　NIO中的channel提供了transferFrom和transferTo两个方法，他们可以用它实现文件的拷贝工作。那么它与我们常用的bio的拷贝有什么区别呢！下面的示例将给你做一个简单的示例。</p>
<h2 id="u793A_u4F8B"><a href="#u793A_u4F8B" class="headerlink" title="示例"></a>示例</h2><p>　　java里，我们可以直接操作InputStream，OutputStream进行文件的拷贝工作。但是在nio里面，我们则可以利用channel的transferFrom或transferTo进行拷贝。那么这两种方式有什么区别呢？通过下面的代码我们进行简单的性能测试，一窥究竟！<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.File;</span><br><span class="line"><span class="keyword">import</span> java.io.FileInputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.FileOutputStream;</span><br><span class="line"><span class="keyword">import</span> java.nio.MappedByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.FileChannel;</span><br><span class="line"><span class="keyword">import</span> java.nio.file.*;</span><br><span class="line"><span class="keyword">import</span> java.util.Date;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FileChannelDemo</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String srcFilePath = <span class="string">"D:\\迅雷下载\\PowerDesigner165_Evaluation.1428562995.exe"</span>;</span><br><span class="line">    <span class="keyword">private</span> String descFilePath = <span class="string">"D:\\迅雷下载\\PowerDesigner165_Evaluation.1428562995.exe.bak"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> Path copy_from = Paths.get(srcFilePath);</span><br><span class="line">    <span class="keyword">final</span> Path copy_to = Paths.get(descFilePath);</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">filesCopyTest</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">int</span> times = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">long</span> totalTime = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i=<span class="number">0</span>; i&lt;<span class="number">10</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">try</span>&#123;</span><br><span class="line">                <span class="keyword">long</span> startTime = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">                java.nio.file.Files.copy(copy_from, copy_to, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);</span><br><span class="line">                <span class="keyword">long</span> endTime = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">                times ++;</span><br><span class="line">                <span class="keyword">long</span> cost = endTime - startTime;</span><br><span class="line">                totalTime += cost;</span><br><span class="line">                System.out.println(String.format(<span class="string">"第%d次耗时%d毫秒"</span>, i, cost));</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">new</span> File(descFilePath).delete();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(String.format(<span class="string">"执行%d次，耗时%d毫秒"</span>, times, totalTime));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">mappedByteBufferTest</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">int</span> times = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">long</span> totalTime = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i=<span class="number">0</span>; i&lt;<span class="number">10</span>; i++) &#123;</span><br><span class="line">            FileChannel fileChannel_from = <span class="keyword">null</span>;</span><br><span class="line">            FileChannel fileChannel_to = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                fileChannel_from = <span class="keyword">new</span> FileInputStream(srcFilePath).getChannel();</span><br><span class="line">                fileChannel_to = <span class="keyword">new</span> FileOutputStream(descFilePath).getChannel();</span><br><span class="line">                <span class="keyword">long</span> startTime = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">                <span class="comment">//MappedByteBuffer需要占用“双倍”的内存（对象JVM堆内存和Direct Byte Buffer内存）</span></span><br><span class="line">                <span class="comment">//可以通过-XX:MaxDirectMemorySize参数设置后者最大大小</span></span><br><span class="line">                MappedByteBuffer buffer = fileChannel_from.map(FileChannel.MapMode.READ_ONLY, <span class="number">0</span>, fileChannel_from.size());</span><br><span class="line"></span><br><span class="line">                fileChannel_to.write(buffer);</span><br><span class="line">                fileChannel_to.force(<span class="keyword">true</span>);</span><br><span class="line">                buffer.clear();</span><br><span class="line">                <span class="keyword">long</span> endTime = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">                times ++;</span><br><span class="line">                <span class="keyword">long</span> cost = endTime - startTime;</span><br><span class="line">                totalTime += cost;</span><br><span class="line">                System.out.println(String.format(<span class="string">"第%d次耗时%d毫秒"</span>, i, cost));</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    fileChannel_from.close();</span><br><span class="line">                    fileChannel_to.close();</span><br><span class="line">                &#125;<span class="keyword">catch</span> (Exception e)&#123;&#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">new</span> File(descFilePath).delete();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(String.format(<span class="string">"执行%d次，耗时%d毫秒"</span>, times, totalTime));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">transferFromTest</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">int</span> times = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">long</span> totalTime = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i=<span class="number">0</span>; i&lt;<span class="number">10</span>; i++) &#123;</span><br><span class="line">            FileChannel in = <span class="keyword">null</span>;</span><br><span class="line">            FileChannel out = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                in = <span class="keyword">new</span> FileInputStream(srcFilePath).getChannel();</span><br><span class="line">                out = <span class="keyword">new</span> FileOutputStream(descFilePath).getChannel();</span><br><span class="line">                <span class="keyword">long</span> startTime = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">                out.transferFrom(in, <span class="number">0</span>, in.size());</span><br><span class="line"></span><br><span class="line">                <span class="keyword">long</span> endTime = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">                times ++;</span><br><span class="line">                <span class="keyword">long</span> cost = endTime - startTime;</span><br><span class="line">                totalTime += cost;</span><br><span class="line">                System.out.println(String.format(<span class="string">"第%d次耗时%d毫秒"</span>, i, cost));</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    in.close();</span><br><span class="line">                    out.close();</span><br><span class="line">                &#125;<span class="keyword">catch</span> (Exception e)&#123;&#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">new</span> File(descFilePath).delete();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(String.format(<span class="string">"执行%d次，耗时%d毫秒"</span>, times, totalTime));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">transferToTest</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">int</span> times = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">long</span> totalTime = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i=<span class="number">0</span>; i&lt;<span class="number">10</span>; i++) &#123;</span><br><span class="line">            FileChannel in = <span class="keyword">null</span>;</span><br><span class="line">            FileChannel out = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">//FileChannel.open(copy_from, StandardOpenOption.READ);</span></span><br><span class="line">                <span class="comment">//FileChannel.open(copy_to, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);</span></span><br><span class="line">                in = <span class="keyword">new</span> FileInputStream(srcFilePath).getChannel();</span><br><span class="line">                out = <span class="keyword">new</span> FileOutputStream(descFilePath).getChannel();</span><br><span class="line">                <span class="keyword">long</span> startTime = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">                in.transferTo(<span class="number">0</span>, in.size(), out);</span><br><span class="line"></span><br><span class="line">                <span class="keyword">long</span> endTime = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">                times ++;</span><br><span class="line">                <span class="keyword">long</span> cost = endTime - startTime;</span><br><span class="line">                totalTime += cost;</span><br><span class="line">                System.out.println(String.format(<span class="string">"第%d次耗时%d毫秒"</span>, i, cost));</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    in.close();</span><br><span class="line">                    out.close();</span><br><span class="line">                &#125;<span class="keyword">catch</span> (Exception e)&#123;&#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">new</span> File(descFilePath).delete();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(String.format(<span class="string">"执行%d次，耗时%d毫秒"</span>, times, totalTime));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">generalIOTest</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">int</span> times = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">long</span> totalTime = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i=<span class="number">0</span>; i&lt;<span class="number">10</span>; i++) &#123;</span><br><span class="line">            FileInputStream in = <span class="keyword">null</span>;</span><br><span class="line">            FileOutputStream out = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                in = <span class="keyword">new</span> FileInputStream(srcFilePath);</span><br><span class="line">                out = <span class="keyword">new</span> FileOutputStream(descFilePath);</span><br><span class="line">                <span class="keyword">long</span> startTime = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">                <span class="keyword">int</span> c;</span><br><span class="line">                <span class="keyword">byte</span> buffer[]=<span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">4096</span>];</span><br><span class="line">                <span class="keyword">while</span>( (c = in.read(buffer)) != -<span class="number">1</span>) &#123;</span><br><span class="line">                    out.write(buffer, <span class="number">0</span>, c);</span><br><span class="line">                &#125;</span><br><span class="line">                out.flush();</span><br><span class="line">                <span class="keyword">long</span> endTime = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">                times ++;</span><br><span class="line">                <span class="keyword">long</span> cost = endTime - startTime;</span><br><span class="line">                totalTime += cost;</span><br><span class="line">                System.out.println(String.format(<span class="string">"第%d次耗时%d毫秒"</span>, i+<span class="number">1</span>, cost));</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    in.close();</span><br><span class="line">                    out.close();</span><br><span class="line">                &#125;<span class="keyword">catch</span> (Exception e)&#123;&#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">new</span> File(descFilePath).delete();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(String.format(<span class="string">"执行%d次，耗时%d毫秒"</span>, times, totalTime));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="comment">//new FileChannelDemo().transferFromTest();</span></span><br><span class="line">        <span class="keyword">new</span> FileChannelDemo().transferToTest();</span><br><span class="line">        <span class="comment">//new FileChannelDemo().generalIOTest();</span></span><br><span class="line">        <span class="comment">//new FileChannelDemo().mappedByteBufferTest();</span></span><br><span class="line">        <span class="comment">//new FileChannelDemo().filesCopyTest();</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>　　分别执行main方法中对用的方法，并对执行耗时进行对比，我们可以分析采用transferFrom、transferTo和java.nio.file.Files.copy的速度最快，mappedByteBuffer耗内存，普通的io操作耗时最大，这个为什么呢？这里就设计到目前非常火的零拷贝的概念了！这里简单的讲解一下，以后单独拿一篇文件进行讲解！</p>
<h2 id="u96F6_u62F7_u8D1D"><a href="#u96F6_u62F7_u8D1D" class="headerlink" title="零拷贝"></a>零拷贝</h2><p>　　普通的文件拷贝操作：将数据从磁盘读入操作系统内核，再由内核拷贝到jvm供程序使用，程序获得数据后再拷贝到操作系统内核中，最后写入磁盘。这样就完成了一次完成的磁盘拷贝过程<br>　　零拷贝操作：可以看到，应用程序只是起到缓存数据并将其传回到套接字的作用而以，别无他用。这一步省略，transferFrom或transferTo将数据从磁盘读入操作系统内核，然后直接写入磁盘。这样就保证了更快的速度！<br>源码下载地址：<a href="/code/filechannel-demo.rar">filechannel-demo.rar</a></p>
<h2 id="u53C2_u8003_u5730_u5740"><a href="#u53C2_u8003_u5730_u5740" class="headerlink" title="参考地址"></a>参考地址</h2><p><a href="http://my.oschina.net/feichexia/blog/212318?fromerr=dNsPLlUP" target="_blank" rel="external">http://my.oschina.net/feichexia/blog/212318?fromerr=dNsPLlUP</a></p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2015/12/23/nio/4.FileChannel文件拷贝/" data-id="cijzzpv6t00501cfspr7nq6kq" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/java/">java</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/socket/">socket</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-nio/3.NIO入门" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2015/12/23/nio/3.NIO入门/" class="article-date">
  <time datetime="2015-12-22T19:00:00.000Z" itemprop="datePublished">2015-12-23</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/nio/">nio</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2015/12/23/nio/3.NIO入门/">NIO入门</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="u7B80_u4ECB"><a href="#u7B80_u4ECB" class="headerlink" title="简介"></a>简介</h2><p>　　Java NIO 由Channels、Buffers和Selectors等几个核心部分组成。虽然Java NIO 中除此之外还有很多类和组件，但在我看来，Channel，Buffer 和 Selector 构成了核心的API。其它组件，如Pipe和FileLock，只不过是与三个核心组件共同使用的工具类。</p>
<h2 id="Channel"><a href="#Channel" class="headerlink" title="Channel"></a>Channel</h2><p>　　基本上，所有的 IO 在 NIO 中都从一个 Channel 开始。Channel 可以理解为管道（流），即所有的通信数据都是在Channel中流通的。 我们既可以从通道中读取数据，又可以写数据到通道(数据可以从Channel读到Buffer中，也可以从 Buffer 写到Channel中)，但流的读写通常是单向的。在JAVA NIO中主要的Channel实现包括如下几种。涵盖了UDP 和 TCP 网络IO，以及文件IO。<br>　　1. FileChannel          从文件中读写数据<br>　　2. DatagramChannel      通过UDP读写网络中的数据<br>　　3. SocketChannel        通过TCP读写网络中的数据<br>　　4. ServerSocketChannel  监听新进来的TCP连接，像Web服务器那样，对每一个新进来的连接都会创建一个SocketChannel。</p>
<h2 id="Buffer"><a href="#Buffer" class="headerlink" title="Buffer"></a>Buffer</h2><p>　　Java NIO中的Buffer用于和NIO通道进行交互。如你所知，数据是从通道读入缓冲区，从缓冲区写入到通道中的。缓冲区本质上是一块可以写入数据，然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象，并提供了一组方法，用来方便的访问该块内存。<br>　　关键的Buffer实现包括：ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer和ShortBuffer。这些Buffer覆盖了我们能通过IO发送的基本数据类型：byte, short, int, long, float, double 和 char。其实，Java NIO 还有个 MappedByteBuffer，用于表示内存映射文件。如果有兴趣，大家可以看看zbus开源项目，其用它实现了消息队列的离线保存。<br>　　ByteBuffer是一个抽象类，它有两种实现：HeapByteBuffer 和 DirectByteBuffer。java.nio.ByteBuffer.allocate(int)返回的是HeapByteBuffer，java.nio.ByteBuffer.allocateDirect(int)返回的是DirectByteBuffer。<br>　　使用Buffer读写数据一般遵循四个步骤：1.写入数据到Buffer  2.调用flip()方法  3.从Buffer中读取数据  4.调用clear()方法或者compact()方法。<br>　　当向buffer写入数据时，buffer会记录下写了多少数据。一旦要读取数据，需要通过flip()方法将Buffer从写模式切换到读模式。在读模式下，可以读取之前写入到buffer的所有数据。一旦读完了所有的数据，就需要清空缓冲区，让它可以再次被写入。有两种方式能清空缓冲区：调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处，新写入的数据将放到缓冲区未读数据的后面。<br>　　理解Buffer的工作原理，需要熟悉它的三个属性：capacity、position和limit。position和limit的含义取决于Buffer处在读模式还是写模式。不管Buffer处在什么模式，capacity的含义总是一样的。下图解释了capacity，position和limit在读写模式中的说明，详细的解释在插图后面。<br><img src="/images/buffers-modes.png" alt="buffers-modes.png"></p>
<h3 id="allocate"><a href="#allocate" class="headerlink" title="allocate"></a>allocate</h3><p>　　要想获得一个Buffer对象首先要进行分配，每一个Buffer类都有一个静态的allocate方法。这里有一个分配48字节capacity的ByteBuffer的例子：ByteBuffer buf = ByteBuffer.allocate(48);<br>　　ByteBuffer提供了两种静态实例方式allocate和allocateDirect。。第一种分配方式产生的内存开销是在JVM中的，而另外一种的分配方式产生的开销在JVM之外，以就是系统级的内存分配。当Java程序接收到外部传来的数据时，首先是被系统内存所获取，然后在由系统内存复制复制到JVM内存中供Java程序使用。所以在另外一种分配方式中，能够省去复制这一步操作，效率上会有所提高。可是系统级内存的分配比起JVM内存的分配要耗时得多，所以并非不论什么时候allocateDirect的操作效率都是最高的。Sun推荐将以字节为单位的直接型缓冲区allocateDirect用于与大型文件相关并具有较长生命周期的缓冲区。</p>
<h3 id="capacity"><a href="#capacity" class="headerlink" title="capacity"></a>capacity</h3><p>　　作为一个内存块，Buffer有一个固定的大小值，也叫 capacity。只能往里写capacity个byte、long，char等类型。一旦Buffer满了，需要将其清空（通过读数据或者清除数据）才能继续写数据往里写数据。</p>
<h3 id="position"><a href="#position" class="headerlink" title="position"></a>position</h3><p>　　当写数据到Buffer中时，position表示当前的位置，初始的position值为0。当一个byte、long等数据写到Buffer后，position会向前移动到下一个可插入数据的Buffer单元，position最大可为capacity – 1。<br>　　当读取数据时，也是从某个特定位置读。当将Buffer从写模式切换到读模式，position会被重置为0，当从Buffer的position处读取数据时，position向前移动到下一个可读的位置。</p>
<h3 id="limit"><a href="#limit" class="headerlink" title="limit"></a>limit</h3><p>　　在写模式下，Buffer的limit表示你最多能往Buffer里写多少数据，此时limit等于Buffer的capacity。<br>　　当切换Buffer到读模式时，limit表示你最多能读到多少数据。因此，当切换Buffer到读模式时，limit会被设置成写模式下的position值。换句话说，你能读到之前写入的所有数据（limit被设置成已写数据的数量，这个值在写模式下就是position）</p>
<h3 id="flip"><a href="#flip" class="headerlink" title="flip"></a>flip</h3><p>　　flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0，并将limit设置成之前position的值。换句话说，position现在用于标记读的位置，limit表示之前写进了多少个byte、char，现在能读取多少个byte、char。</p>
<h3 id="rewind"><a href="#rewind" class="headerlink" title="rewind"></a>rewind</h3><p>　　rewind方法将position设回0，所以你可以重读Buffer中的所有数据。limit保持不变，仍然表示能从Buffer中读取多少个元素（byte、char等）。</p>
<h3 id="clear"><a href="#clear" class="headerlink" title="clear"></a>clear</h3><p>　　调用clear()方法，position将被设回0，limit被设置成capacity的值(buffer的初始状态，此时可以向该buffer重新写入数据)。换句话说，Buffer被清空了（其实Buffer中的数据并未清除，只是被遗忘了，不能再被访问，因为不知道要从哪儿开始访问，到哪儿结束）</p>
<h3 id="compact"><a href="#compact" class="headerlink" title="compact"></a>compact</h3><p>　　compact()方法将所有未读的数据拷贝到Buffer起始处，然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样，设置成capacity。现在Buffer准备好写数据了，但是不会覆盖未读的数据。</p>
<h3 id="mark_u4E0Ereset"><a href="#mark_u4E0Ereset" class="headerlink" title="mark与reset"></a>mark与reset</h3><p>　　mark()方法可以标记Buffer中的一个特定position，之后可以通过调用Buffer.reset()方法恢复到这个position。例如：<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">buffer.mark();</span><br><span class="line">//call buffer.get() a couple of <span class="built_in">times</span>, e.g. during parsing.</span><br><span class="line">buffer.reset();  //<span class="built_in">set</span> position back to mark.</span><br></pre></td></tr></table></figure></p>
<h3 id="equals"><a href="#equals" class="headerlink" title="equals"></a>equals</h3><p>　　当满足下列条件时，表示两个Buffer相等：<br>　　1. 有相同的类型（byte、char、int等）。<br>　　2. Buffer中剩余的byte、char等的个数相等。<br>　　3. Buffer中所有剩余的byte、char等都相同。<br>　　可以看到，equals只是比较Buffer的一部分，不是每一个在它里面的元素都比较。实际上，它只比较Buffer中的剩余元素。剩余元素是从 position到limit之间的元素。</p>
<h3 id="compareTo"><a href="#compareTo" class="headerlink" title="compareTo"></a>compareTo</h3><p>　　compareTo()方法比较两个Buffer的剩余元素(byte、char等)，如果满足下列条件，则认为一个Buffer “小于” 另一个Buffer：<br>　　1. 第一个不相等的元素小于另一个Buffer中对应的元素；<br>　　2. 所有元素都相等，但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。</p>
<h2 id="Scatter/Gather"><a href="#Scatter/Gather" class="headerlink" title="Scatter/Gather"></a>Scatter/Gather</h2><p>　　scatter（分散）从Channel中读取时将读取的数据写入多个buffer中，因此，Channel将从Channel中读取的数据scatter到多个Buffer中。<br>　　gather（聚集）写入Channel时将多个buffer的数据写入同一个Channel，因此，Channel将多个Buffer中的数据gather后发送到Channel。<br>　　scatter/gather经常用于需要将传输的数据分开处理的场合，例如传输一个由消息头和消息体组成的消息，你可能会将消息体和消息头分散到不同的buffer中，这样你可以方便的处理消息头和消息体。<br>　　注意，这里的scatter和gather的概念，都是相对于Channel的<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ByteBuffer header = ByteBuffer.allocate(<span class="number">128</span>);</span><br><span class="line">ByteBuffer body   = ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line">ByteBuffer[] bufferArray = &#123; header, body &#125;;</span><br><span class="line">channel.read(bufferArray);</span><br></pre></td></tr></table></figure></p>
<p>　　buffer首先被插入到数组，然后再将数组作为channel.read()的输入参数。read()方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer，当一个buffer被写满后，channel紧接着向另一个buffer中写。<br>　　Scattering Reads在移动下一个buffer前，必须填满当前的buffer，这也意味着<strong>它不适用于动态消息</strong>(译者注：消息大小不固定)。换句话说，如果存在消息头和消息体，消息头必须完成填充（例如 128byte），Scattering Reads才能正常工作。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ByteBuffer header = ByteBuffer.allocate(<span class="number">128</span>);</span><br><span class="line">ByteBuffer body   = ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line"><span class="comment">//write data into buffers</span></span><br><span class="line">ByteBuffer[] bufferArray = &#123; header, body &#125;;</span><br><span class="line">channel.write(bufferArray);</span><br></pre></td></tr></table></figure></p>
<p>　　buffers数组是write()方法的入参，write()方法会按照buffer在数组中的顺序，将数据写入到channel，注意只有position和limit之间的数据才会被写入。因此，如果一个buffer的容量为128byte，但是仅仅包含58byte的数据，那么这58byte的数据将被写入到channel中。因此与Scattering Reads相反，Gathering Writes能较好的处理动态消息。</p>
<h2 id="Selector"><a href="#Selector" class="headerlink" title="Selector"></a>Selector</h2><p>　　Selector（选择器）能够检测一到多个NIO通道，并能够知晓通道是否为诸如读写事件做好准备的组件。这样，一个单独的线程可以管理多个channel，从而管理多个网络连接。所以通过Selector，我们可以单个线程处理多个Channel（在Socket中一个线程处理一个连接）。<br>　　如果你的应用打开了多个连接（通道），但每个连接的流量都很低，使用Selector就会很方便，例如在一个聊天服务器中。下面是在一个单线程中使用一个Selector处理3个Channel的图示：<img src="/images/selector-channel.png" alt="selector-channel.png"><br>　　仅用单个线程来处理多个Channels的好处是：只需要更少的线程来处理通道。事实上，可以只用一个线程处理所有的通道。对于操作系统来说，线程之间上下文切换的开销很大，而且每个线程都要占用系统的一些资源（如内存）。因此，使用的线程越少越好。但是，需要记住，现代的操作系统和CPU在多任务方面表现的越来越好，所以多线程的开销随着时间的推移，变得越来越小了。实际上，如果一个CPU有多个内核，不使用多任务可能是在浪费CPU能力。</p>
<h3 id="Selector_u521B_u5EFA"><a href="#Selector_u521B_u5EFA" class="headerlink" title="Selector创建"></a>Selector创建</h3><p>　　通过调用Selector.open()方法创建一个Selector，如下：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Selector selector = Selector.open();</span><br></pre></td></tr></table></figure></p>
<h3 id="u5411Selector_u6CE8_u518C_u901A_u9053"><a href="#u5411Selector_u6CE8_u518C_u901A_u9053" class="headerlink" title="向Selector注册通道"></a>向Selector注册通道</h3><p>　　为了将Channel和Selector配合使用，必须将channel注册到selector上。通过SelectableChannel.register()方法来实现，如下：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">channel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line">SelectionKey key = channel.register(selector, Selectionkey.OP_READ);</span><br></pre></td></tr></table></figure></p>
<p>　　与Selector一起使用时，Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用，因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。注意register()方法的第二个参数，这是一个interest集合，意思是在通过Selector监听Channel时对什么事件感兴趣，可以监听四种不同类型的事件：Connect、Accept、Read和Write。<br>　　通道触发了一个事件的意思是该事件已经就绪。所以，某个channel成功连接到另一个服务器称为“连接就绪”，一个server socket channel准备好接收新进入的连接称为“接收就绪”，一个有数据可读的通道可以说是“读就绪”，等待写数据的通道可以说是“写就绪”。这四种事件用SelectionKey的四个常量来表示：SelectionKey.OP_CONNECT、SelectionKey.OP_ACCEPT、SelectionKey.OP_READ和SelectionKey.OP_WRITE。当然了，如果你对不止一种事件感兴趣，那么可以用“位或”操作符将常量连接起来：int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;<br>　　要使用Selector，得向Selector注册Channel，然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回，线程就可以处理这些事件，事件的例子有如新连接进来，数据接收等。</p>
<h2 id="SelectionKey"><a href="#SelectionKey" class="headerlink" title="SelectionKey"></a>SelectionKey</h2><p>　　上一节中，当向Selector注册Channel时，register()方法会返回一个SelectionKey对象。这个对象包含了的属性有：interest集合、ready集合、Channel和Selector和附加的对象（可选）</p>
<h3 id="interest_u96C6_u5408"><a href="#interest_u96C6_u5408" class="headerlink" title="interest集合"></a>interest集合</h3><p>　　就像向Selector注册通道一节中所描述的，interest集合是你所选择的感兴趣的事件集合。可以通过SelectionKey读写interest集合，像这样：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> interestSet = selectionKey.interestOps();</span><br><span class="line"> </span><br><span class="line"><span class="keyword">boolean</span> isInterestedInAccept  = (interestSet &amp; SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT；</span><br><span class="line"><span class="keyword">boolean</span> isInterestedInConnect = interestSet &amp; SelectionKey.OP_CONNECT;</span><br><span class="line"><span class="keyword">boolean</span> isInterestedInRead    = interestSet &amp; SelectionKey.OP_READ;</span><br><span class="line"><span class="keyword">boolean</span> isInterestedInWrite   = interestSet &amp; SelectionKey.OP_WRITE;</span><br></pre></td></tr></table></figure></p>
<h3 id="ready_u96C6_u5408"><a href="#ready_u96C6_u5408" class="headerlink" title="ready集合"></a>ready集合</h3><p>　　ready 集合是通道已经准备就绪的操作的集合。在一次选择(Selection)之后，你会首先访问这个ready set。Selection将在下一小节进行解释。可以这样访问ready集合：int readySet = selectionKey.readyOps();<br>　　可以用像检测interest集合那样的方法，来检测channel中什么事件或操作已经就绪。但是，也可以使用以下四个方法，它们都会返回一个布尔类型：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">selectionKey.isAcceptable();</span><br><span class="line">selectionKey.isConnectable();</span><br><span class="line">selectionKey.isReadable();</span><br><span class="line">selectionKey.isWritable();</span><br></pre></td></tr></table></figure></p>
<h3 id="Channel_+_Selector"><a href="#Channel_+_Selector" class="headerlink" title="Channel + Selector"></a>Channel + Selector</h3><p>　　从SelectionKey访问Channel和Selector很简单。如下：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Channel  channel  = selectionKey.channel();</span><br><span class="line">Selector selector = selectionKey.selector();</span><br></pre></td></tr></table></figure></p>
<h3 id="u9644_u52A0_u7684_u5BF9_u8C61"><a href="#u9644_u52A0_u7684_u5BF9_u8C61" class="headerlink" title="附加的对象"></a>附加的对象</h3><p>　　可以将一个对象或者更多信息附着到SelectionKey上，这样就能方便的识别某个给定的通道。例如，可以附加与通道一起使用的Buffer，或是包含聚集数据的某个对象。使用方法如下：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">selectionKey.attach(theObject);</span><br><span class="line">Object attachedObj = selectionKey.attachment();</span><br></pre></td></tr></table></figure></p>
<p>　　还可以在用register()方法向Selector注册Channel的时候附加对象。如：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);</span><br></pre></td></tr></table></figure></p>
<p>可以看到，用“位与”操作interest 集合和给定的SelectionKey常量，可以确定某个确定的事件是否在interest 集合中。</p>
<h2 id="Selector_u9009_u62E9_u901A_u9053"><a href="#Selector_u9009_u62E9_u901A_u9053" class="headerlink" title="Selector选择通道"></a>Selector选择通道</h2><p>　　一旦向Selector注册了一或多个通道，就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件（如连接、接受、读或写）已经准备就绪的那些通道。换句话说，如果你对“读就绪”的通道感兴趣，select()方法会返回读事件已经就绪的那些通道。下面是select()方法：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">select</span><span class="params">()</span>  <span class="comment">//阻塞到至少有一个通道在你注册的事件上就绪了。</span></span><br><span class="line"><span class="keyword">int</span> <span class="title">select</span><span class="params">(<span class="keyword">long</span> timeout)</span> <span class="comment">//和select()一样，除了最长会阻塞timeout毫秒(参数)。</span></span><br><span class="line"><span class="keyword">int</span> <span class="title">selectNow</span><span class="params">()</span> <span class="comment">//不管什么通道就绪都立刻返回，此方法执行非阻塞的选择操作。如果自从前一次选择操作后，没有通道变成可选择的，则此方法直接返回零。</span></span></span><br></pre></td></tr></table></figure></p>
<p>　　select()方法返回的int值表示有多少通道已经就绪。亦即自上次调用select()方法后有多少通道变成就绪状态。如果调用select()方法，因为有一个通道变成就绪状态，返回了1，若再次调用select()方法，如果另一个通道就绪了，它会再次返回1。如果对第一个就绪的channel没有做任何操作，现在就有两个就绪的通道，但在每次select()方法调用之间，只有一个通道就绪了。</p>
<h3 id="selectedKeys_28_29"><a href="#selectedKeys_28_29" class="headerlink" title="selectedKeys()"></a>selectedKeys()</h3><p>　　一旦调用了select()方法，并且返回值表明有一个或更多个通道就绪了，然后可以通过调用selector的selectedKeys()方法，访问就绪通道。如下所示：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Set selectedKeys = selector.selectedKeys();</span><br></pre></td></tr></table></figure></p>
<p>　　当像Selector注册Channel时，Channel.register()方法会返回一个SelectionKey对象。这个对象代表了注册到该Selector的通道。可以通过SelectionKey的selectedKeySet()方法访问这些对象。可以遍历这个selectedKeys集合来访问就绪的通道。如下：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Set selectedKeys = selector.selectedKeys();</span><br><span class="line">Iterator keyIterator = selectedKeys.iterator();</span><br><span class="line"><span class="keyword">while</span>(keyIterator.hasNext()) &#123;</span><br><span class="line">    SelectionKey key = keyIterator.next();</span><br><span class="line">    <span class="keyword">if</span>(key.isAcceptable()) &#123;</span><br><span class="line">        <span class="comment">// a connection was accepted by a ServerSocketChannel.</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.isConnectable()) &#123;</span><br><span class="line">        <span class="comment">// a connection was established with a remote server.</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.isReadable()) &#123;</span><br><span class="line">        <span class="comment">// a channel is ready for reading</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.isWritable()) &#123;</span><br><span class="line">        <span class="comment">// a channel is ready for writing</span></span><br><span class="line">    &#125;</span><br><span class="line">    keyIterator.remove();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>　　这个循环遍历selectedKeys集中的每个键，并检测各个键所对应的通道的就绪事件。注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时，Selector会再次将其放入已选择键集中。<br>　　SelectionKey.channel()方法返回的通道需要转型成你要处理的类型，如ServerSocketChannel或SocketChannel等。</p>
<h2 id="wakeUp"><a href="#wakeUp" class="headerlink" title="wakeUp"></a>wakeUp</h2><p>　　某个线程调用select()方法后阻塞了，即使没有通道已经就绪，也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。<br>　　如果有其它线程调用了wakeup()方法，但当前没有线程阻塞在select()方法上，下个调用select()方法的线程会立即“醒来（wake up）”。</p>
<h2 id="close_28_29"><a href="#close_28_29" class="headerlink" title="close()"></a>close()</h2><p>　　用完Selector后调用其close()方法会关闭该Selector，且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。</p>
<h2 id="u793A_u4F8B"><a href="#u793A_u4F8B" class="headerlink" title="示例"></a>示例</h2><p>　　这里有一个完整的示例，打开一个Selector，注册一个通道注册到这个Selector上(通道的初始化过程略去),然后持续监控这个Selector的四种事件（接受，连接，读，写）是否就绪。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">Selector selector = Selector.open();</span><br><span class="line">channel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line">SelectionKey key = channel.register(selector, SelectionKey.OP_READ);</span><br><span class="line"><span class="keyword">while</span>(<span class="keyword">true</span>) &#123;</span><br><span class="line">  <span class="keyword">int</span> readyChannels = selector.select();</span><br><span class="line">  <span class="keyword">if</span>(readyChannels == <span class="number">0</span>) <span class="keyword">continue</span>;</span><br><span class="line">  Set selectedKeys = selector.selectedKeys();</span><br><span class="line">  Iterator keyIterator = selectedKeys.iterator();</span><br><span class="line">  <span class="keyword">while</span>(keyIterator.hasNext()) &#123;</span><br><span class="line">    SelectionKey key = keyIterator.next();</span><br><span class="line">    <span class="keyword">if</span>(key.isAcceptable()) &#123;</span><br><span class="line">        <span class="comment">// a connection was accepted by a ServerSocketChannel.</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.isConnectable()) &#123;</span><br><span class="line">        <span class="comment">// a connection was established with a remote server.</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.isReadable()) &#123;</span><br><span class="line">        <span class="comment">// a channel is ready for reading</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.isWritable()) &#123;</span><br><span class="line">        <span class="comment">// a channel is ready for writing</span></span><br><span class="line">    &#125;</span><br><span class="line">    keyIterator.remove();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h2 id="ServerSocketChannel"><a href="#ServerSocketChannel" class="headerlink" title="ServerSocketChannel"></a>ServerSocketChannel</h2><p>　　ServerSocketChannel 是一个可以监听新进来的TCP连接的通道，就像标准IO中的ServerSocket一样。ServerSocketChannel类在 java.nio.channels包中。这里有个例子：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//打开 ServerSocketChannel</span></span><br><span class="line">ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();</span><br><span class="line"> </span><br><span class="line">serverSocketChannel.socket().bind(<span class="keyword">new</span> InetSocketAddress(<span class="number">9999</span>));</span><br><span class="line"><span class="comment">//可以设置成非阻塞模式。在非阻塞模式下，accept() 方法会立刻返回</span></span><br><span class="line"><span class="comment">//如果还没有新进来的连接，返回的将是null。因此，需要检查返回的SocketChannel是否是null</span></span><br><span class="line">serverSocketChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line"><span class="keyword">while</span>(<span class="keyword">true</span>)&#123;</span><br><span class="line">    <span class="comment">//监听新进来的连接。当 accept()方法返回的时候，它返回一个包含新进来的连接的 SocketChannel。</span></span><br><span class="line">	<span class="comment">//因此，accept()方法会一直阻塞到有新连接到达。</span></span><br><span class="line">	<span class="comment">//通常不会仅仅只监听一个连接，而是在while循环中调用 accept()方法</span></span><br><span class="line">    SocketChannel socketChannel = serverSocketChannel.accept();</span><br><span class="line">    <span class="keyword">if</span>(socketChannel != <span class="keyword">null</span>)&#123;</span><br><span class="line">        <span class="comment">//do something with socketChannel...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">serverSocketChannel.close();</span><br></pre></td></tr></table></figure></p>
<h2 id="SocketChannel"><a href="#SocketChannel" class="headerlink" title="SocketChannel"></a>SocketChannel</h2><p>　　SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel：<br>　　1. 打开一个SocketChannel并连接到互联网上的某台服务器。<br>　　2. 一个新连接到达ServerSocketChannel时，会创建一个SocketChannel。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//打开 SocketChannel</span></span><br><span class="line">SocketChannel socketChannel = SocketChannel.open();</span><br><span class="line">socketChannel.connect(<span class="keyword">new</span> InetSocketAddress(<span class="string">"http://xxx.com"</span>, <span class="number">80</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">//关闭 SocketChannel</span></span><br><span class="line">socketChannel.close();</span><br><span class="line"></span><br><span class="line"><span class="comment">//从channel读取数据</span></span><br><span class="line">ByteBuffer buf = ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line"><span class="keyword">int</span> bytesRead = socketChannel.read(buf);<span class="comment">//将读到的数据放入buf，read()方法返回的int值表示读了多少字节进Buffer里。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//写数据到SocketChannel用的是SocketChannel.write()方法，该方法以一个Buffer作为参数。示例如下：</span></span><br><span class="line">String newData = <span class="string">"New String to write to file..."</span> + System.currentTimeMillis();</span><br><span class="line"> </span><br><span class="line">ByteBuffer buf = ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line">buf.clear();</span><br><span class="line">buf.put(newData.getBytes());</span><br><span class="line"> </span><br><span class="line">buf.flip();</span><br><span class="line"> </span><br><span class="line"><span class="keyword">while</span>(buf.hasRemaining()) &#123;</span><br><span class="line">    channel.write(buf);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h3 id="write"><a href="#write" class="headerlink" title="write"></a>write</h3><p>　　SocketChannel.write()方法的调用是在一个while循环中的。在非阻塞模式下，write()方法在尚未写出任何内容时可能就返回了，即Write()方法无法保证能写多少字节到SocketChannel。所以，我们重复调用write()直到Buffer没有要写的字节为止。<br>　　可以设置 SocketChannel 为非阻塞模式（non-blocking mode）。设置之后，就可以在异步模式下调用connect(), read() 和write()了。</p>
<h3 id="connect"><a href="#connect" class="headerlink" title="connect"></a>connect</h3><p>　　如果SocketChannel在非阻塞模式下，调用connect()时，该方法可能在连接建立之前就返回了。为了确定连接是否建立，可以调用finishConnect()的方法。像这样：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">socketChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line">socketChannel.connect(<span class="keyword">new</span> InetSocketAddress(<span class="string">"http://jenkov.com"</span>, <span class="number">80</span>));</span><br><span class="line"><span class="keyword">while</span>(! socketChannel.finishConnect() )&#123;</span><br><span class="line">    <span class="comment">//wait, or do something else...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h3 id="read"><a href="#read" class="headerlink" title="read"></a>read</h3><p>　　非阻塞模式下，read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值，它会告诉你读取了多少字节。</p>
<h3 id="u975E_u963B_u585E_u6A21_u5F0F_u4E0E_u9009_u62E9_u5668"><a href="#u975E_u963B_u585E_u6A21_u5F0F_u4E0E_u9009_u62E9_u5668" class="headerlink" title="非阻塞模式与选择器"></a>非阻塞模式与选择器</h3><p>　　非阻塞模式与选择器搭配会工作的更好，通过将一或多个SocketChannel注册到Selector，可以询问选择器哪个通道已经准备好了读取，写入等。</p>
<h2 id="Pipe"><a href="#Pipe" class="headerlink" title="Pipe"></a>Pipe</h2><p>　　Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道，从source通道读取。下图是Pipe原理的图示：<img src="/images/pipe.x-ms-bmp" alt="pipe.x-ms-bmp"><br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//创建打开管道</span></span><br><span class="line">Pipe pipe = Pipe.open();</span><br><span class="line"><span class="comment">//向管道写数据，需要访问sink通道</span></span><br><span class="line">Pipe.SinkChannel sinkChannel = pipe.sink();</span><br><span class="line"><span class="comment">//通过调用SinkChannel的write()方法，将数据写入SinkChannel</span></span><br><span class="line">String newData = <span class="string">"New String to write to file..."</span> + System.currentTimeMillis();</span><br><span class="line">ByteBuffer buf = ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line">buf.clear();</span><br><span class="line">buf.put(newData.getBytes());</span><br><span class="line">buf.flip();</span><br><span class="line"><span class="keyword">while</span>(buf.hasRemaining()) &#123;</span><br><span class="line">    sinkChannel.write(buf);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//从读取管道的数据，需要访问source通道</span></span><br><span class="line">Pipe.SourceChannel sourceChannel = pipe.source();</span><br><span class="line"><span class="comment">//调用source通道的read()方法来读取数据，像这样：</span></span><br><span class="line">ByteBuffer buf = ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line"><span class="keyword">int</span> bytesRead = sourceChannel.read(buf);<span class="comment">//read()方法返回的int值会告诉我们多少字节被读进了缓冲区。</span></span><br></pre></td></tr></table></figure></p>
<h2 id="IO_u4E0ENIO"><a href="#IO_u4E0ENIO" class="headerlink" title="IO与NIO"></a>IO与NIO</h2><p>IO：面向流 阻塞IO<br>NIO：面向缓冲 非阻塞IO<br>　　IO面向流意味着每次从流中读一个或多个字节，直至读取所有字节，它们没有被缓存在任何地方。此外，它不能前后移动流中的数据。如果需要前后移动从流中读取的数据，需要先将它缓存到一个缓冲区。<br>　　NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区，需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是，还需要检查是否该缓冲区中包含所有您需要处理的数据。而且，需确保当更多的数据读入缓冲区时，不要覆盖缓冲区里尚未处理的数据</p>
<h3 id="u9762_u5411_u6D41_u4E0E_u9762_u5411_u7F13_u51B2"><a href="#u9762_u5411_u6D41_u4E0E_u9762_u5411_u7F13_u51B2" class="headerlink" title="面向流与面向缓冲"></a>面向流与面向缓冲</h3><p>　　IO面向流意味着每次从流中读一个或多个字节，直至读取所有字节，它们没有被缓存在任何地方。此外，它不能前后移动流中的数据。如果需要前后移动从流中读取的数据，需要先将它缓存到一个缓冲区。<br>　　NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区，需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是，还需要检查是否该缓冲区中包含所有您需要处理的数据。而且，需确保当更多的数据读入缓冲区时，不要覆盖缓冲区里尚未处理的数据</p>
<h3 id="u963B_u585E_u4E0E_u975E_u963B_u585EIO"><a href="#u963B_u585E_u4E0E_u975E_u963B_u585EIO" class="headerlink" title="阻塞与非阻塞IO"></a>阻塞与非阻塞IO</h3><p>　　IO的各种流是阻塞的。这意味着，当一个线程调用read() 或 write()时，该线程被阻塞，直到有一些数据被读取，或数据完全写入。该线程在此期间不能再干任何事情了。<br>　　NIO的非阻塞模式，使一个线程从某通道发送请求读取数据，但是它仅能得到目前可用的数据，如果目前没有数据可用时，就什么都不会获取。而不是保持线程阻塞，所以直至数据变的可以读取之前，该线程可以继续做其他的事情。非阻塞写也是如此。一个线程请求写入一些数据到某通道，但不需要等待它完全写入，这个线程同时可以去做别的事情。线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作，所以一个单独的线程现在可以管理多个输入和输出通道（channel）。</p>
<h3 id="u9009_u62E9_u5668_uFF08Selectors_uFF09"><a href="#u9009_u62E9_u5668_uFF08Selectors_uFF09" class="headerlink" title="选择器（Selectors）"></a>选择器（Selectors）</h3><p>　　NIO的选择器允许一个单独的线程来监视多个输入通道，你可以注册多个通道使用一个选择器，然后使用一个单独的线程来“选择”通道：这些通道里已经有可以处理的输入，或者选择已准备写入的通道。这种选择机制，使得一个单独的线程很容易来管理多个通道。</p>
<h2 id="u603B_u7ED3"><a href="#u603B_u7ED3" class="headerlink" title="总结"></a>总结</h2><ul>
<li>Buffer：缓冲区，用来临时存放输入或输出数据。</li>
<li>Channel：数据传输通道，用来把Buffer中的数据写入到数据源，或者把数据源中的数据读入到Buffer。</li>
<li>Selector：用来支持异步I/O操作，也叫非阻塞I/O操作。</li>
</ul>
<p>nio包中主要通过下面两个方面来提高I/O操作效率:</p>
<ul>
<li>通过Buffer和Channel来提高I/O操作的速度。</li>
<li>通过Selector来支持非阻塞I/O操作。<br>　　最后，我们列出几张图：<br><img src="/images/io-nio.png" alt="io-nio.png"><br><img src="/images/serversocketchannel.png" alt="serversocketchannel.png"><br><img src="/images/socketchannel.png" alt="socketchannel.png"><h2 id="u53C2_u8003_u5730_u5740"><a href="#u53C2_u8003_u5730_u5740" class="headerlink" title="参考地址"></a>参考地址</h2><a href="http://ifeve.com/overview/" target="_blank" rel="external">http://ifeve.com/overview/</a><br><a href="http://www.tuicool.com/articles/vayummi" target="_blank" rel="external">http://www.tuicool.com/articles/vayummi</a><br><a href="http://www.udpwork.com/item/6095.html" target="_blank" rel="external">http://www.udpwork.com/item/6095.html</a><br><a href="http://marlonyao.iteye.com/blog/1005690" target="_blank" rel="external">http://marlonyao.iteye.com/blog/1005690</a></li>
</ul>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2015/12/23/nio/3.NIO入门/" data-id="cijzzpv6x00541cfsskxmugyr" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/java/">java</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/socket/">socket</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-nio/2.SSL Socket" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2015/12/23/nio/2.SSL Socket/" class="article-date">
  <time datetime="2015-12-22T18:00:00.000Z" itemprop="datePublished">2015-12-23</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/nio/">nio</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2015/12/23/nio/2.SSL Socket/">SSLSocket入门</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="SSL/TLS__u534F_u8BAE"><a href="#SSL/TLS__u534F_u8BAE" class="headerlink" title="SSL/TLS 协议"></a>SSL/TLS 协议</h2><p>　　SSL/TLS 协议（RFC2246 RFC4346）处于 TCP/IP 协议与各种应用层协议之间，为数据通讯提供安全支持。从协议内部的功能层面上来看，SSL/TLS 协议可分为两层：<br>　　1. SSL/TLS 记录协议（SSL/TLS Record Protocol），它建立在可靠的传输层协议（如TCP）之上，为上层协议提供数据封装、压缩、加密等基本功能。<br>　　2. SSL/TLS 握手协议（SSL/TLS Handshake Protocol），它建立在 SSL/TLS记录协议之上，用于在实际的数据传输开始前，通讯双方进行身份认证、协商加密算法、交换加密密钥等初始化协商功能。<br>　　从协议使用方式来看，又可以分成两种类型：<br>　　1. SSL/TLS 单向认证，就是用户到服务器之间只存在单方面的认证，即<strong>客户端会认证服务器端身份，而服务器端不会去对客户端身份进行验证</strong>。首先，客户端发起握手请求，服务器收到握手请求后，会选择适合双方的协议版本和加密方式。然后，再将协商的结果和服务器端的公钥一起发送给客户端。<em>客户端利用服务器端的公钥，对要发送的数据进行加密，并发送给服务器端</em>。服务器端收到后，会用本地私钥对收到的客户端加密数据进行解密。然后，通讯双方都会使用这些数据来产生双方之间通讯的加密密钥。接下来，双方就可以开始安全通讯过程了。<br>　　2.SSL/TLS 双向认证，就是双方都会互相认证，也就是两者之间将会交换证书。基本的过程和单向认证完全一样，只是在协商阶段多了几个步骤。在服务器端将协商的结果和服务器端的公钥一起发送给客户端后，会请求客户端的证书，客户端则会将证书发送给服务器端。然后，在客户端给服务器端发送加密数据后，客户端会将私钥生成的数字签名发送给服务器端。而服务器端则会用客户端证书中的公钥来验证数字签名的合法性。建立握手之后过程则和单向通讯完全保持一致。<br>　　SSL/TLS 协议建立通讯的基本流程如图所示<img src="/images/ssl-tls.png" alt="ssl-tls.png"></p>
<p>　　步骤 1. ClientHello – 客户端发送所支持的 SSL/TLS 最高协议版本号和所支持的加密算法集合及压缩方法集合等信息给服务器端。<br>　　步骤 2. ServerHello – 服务器端收到客户端信息后，选定双方都能够支持的 SSL/TLS 协议版本和加密方法及压缩方法，返回给客户端。<br>　　步骤 3（可选）. SendCertificate – 服务器端发送服务端证书给客户端。<br>　　步骤 4（可选）. RequestCertificate – 如果选择双向验证，服务器端向客户端请求客户端证书。<br>　　步骤 5. ServerHelloDone – 服务器端通知客户端初始协商结束。<br>　　步骤 6（可选）. ResponseCertificate – 如果选择双向验证，客户端向服务器端发送客户端证书。<br>　　步骤 7. ClientKeyExchange – 客户端使用服务器端的公钥，对客户端公钥和密钥种子进行加密，再发送给服务器端。<br>　　步骤 8（可选）. CertificateVerify – 如果选择双向验证，客户端用本地私钥生成数字签名，并发送给服务器端，让其通过收到的客户端公钥进行身份验证。<br>　　步骤 9. CreateSecretKey – 通讯双方基于密钥种子等信息生成通讯密钥。<br>　　步骤 10. ChangeCipherSpec – 客户端通知服务器端已将通讯方式切换到加密模式。<br>　　步骤 11. Finished – 客户端做好加密通讯的准备。<br>　　步骤 12. ChangeCipherSpec – 服务器端通知客户端已将通讯方式切换到加密模式。<br>　　步骤 13. Finished – 服务器做好加密通讯的准备。<br>　　步骤 14. Encrypted/DecryptedData – 双方使用客户端密钥，通过对称加密算法对通讯内容进行加密。<br>　　步骤 15. ClosedConnection – 通讯结束后，任何一方发出断开 SSL 连接的消息。<br>　　除了以上的基本流程，SSL/TLS 协议本身还有一些概念需要在此解释说明一下。<br>　　<strong>Key</strong>：Key 是一个比特（bit）字符串，用来加密解密数据的，就像是一把开锁的钥匙。<br>　　<strong>对称算法</strong>（symmetric cryptography）：就是需要双方使用一样的 key 来加密解密消息算法，常用密钥算法有 Data Encryption Standard（DES）、triple-strength DES（3DES）、Rivest Cipher 2 （RC2）和 Rivest Cipher 4（RC4）。因为对称算法效率相对较高，因此 SSL 会话中的敏感数据都用通过密钥算法加密。<br>　　<strong>非对称算法</strong>（asymmetric cryptography）：就是 key 的组成是公钥私钥对 （key-pair），公钥传递给对方私钥自己保留。公钥私钥算法是互逆的，一个用来加密，另一个可以解密。常用的算法有 Rivest Shamir Adleman（RSA）、Diffie-Hellman（DH）。非对称算法计算量大比较慢，因此仅适用于少量数据加密，如对密钥加密，而不适合大量数据的通讯加密。<br>　　<strong>公钥证书</strong>（public key certificate）：公钥证书类似数字护照，由受信机构颁发。受信组织的公钥证书就是 certificate authority（CA）。多证书可以连接成证书串，第一个是发送人，下一个是给其颁发证书实体，往上到根证书是世界范围受信组织，包括 VeriSign, Entrust, 和 GTE CyberTrust。公钥证书让非对称算法的公钥传递更安全，可以避免身份伪造，比如 C 创建了公钥私钥，对并冒充 A 将公钥传递给 B，这样 C 与 B 之间进行的通讯会让 B 误认是 A 与 B 之间通讯。<br>　　<strong>加密哈希功能</strong>（Cryptographic Hash Functions）： 加密哈希功能与 checksum 功能相似。不同之处在于，checksum 用来侦测意外的数据变化而前者用来侦测故意的数据篡改。数据被哈希后产生一小串比特字符串，微小的数据改变将导致哈希串的变化。发送加密数据时，SSL 会使用加密哈希功能来确保数据一致性，用来阻止第三方破坏通讯数据完整性。SSL 常用的哈希算法有 Message Digest 5（MD5）和 Secure Hash Algorithm（SHA）。<br>　　<strong>消息认证码</strong>（Message Authentication Code）： 消息认证码与加密哈希功能相似，除了它需要基于密钥。密钥信息与加密哈希功能产生的数据结合就是哈希消息认证码（HMAC）。如果 A 要确保给 B 发的消息不被 C 篡改，他要按如下步骤做 –A 首先要计算出一个 HMAC 值，将其添加到原始消息后面。用 A 与 B 之间通讯的密钥加密消息体，然后发送给 B。B 收到消息后用密钥解密，然后重新计算出一个 HMAC，来判断消息是否在传输中被篡改。SSL 用 HMAC 来保证数据传输的安全。<br>　　<strong>数字签名</strong>（Digital Signature）：一个消息的加密哈希被创建后，哈希值用发送者的私钥加密，加密的结果就是叫做数字签名。</p>
<h2 id="JSSE_uFF08Java_Secure_Socket_Extension_uFF09"><a href="#JSSE_uFF08Java_Secure_Socket_Extension_uFF09" class="headerlink" title="JSSE（Java Secure Socket Extension）"></a>JSSE（Java Secure Socket Extension）</h2><p>　　在 Java SDK 中有一个叫 JSSE（javax.net.ssl）包，这个包中提供了一些类来建立 SSL/TLS连接。通过这些类，开发者就可以忽略复杂的协议建立流程，较为简单地在网络上建成安全的通讯通道。JSSE 包中主要包括以下一些部分：<br>　　1. 安全套接字（secure socket）和安全服务器端套接字<br>　　2. 非阻塞式 SSL/TLS 数据处理引擎（SSLEngine）<br>　　3. 套接字创建工厂 , 用来产生 SSL 套接字和服务器端套接字<br>　　4. 套接字上下文 , 用来保存用于创建和数据引擎处理过程中的信息<br>　　5. 符合 X.509 规范密码匙和安全管理接口</p>
<p>　　下面将通过一个简单的例子来展示如何通过 JSSE，在客户端和服务器端建立一个 SSL/TLS 连接。设计两个类 SSLClient 和 SSLServer，分别来表示客户端和服务器端。客户端将会向服务器端发起连接请求，在通过服务器端验证建立 SSL 连接后，服务器端将会向客户端发送一串内容，客户端将会把收到的内容打印出来。<br>　　SSL Server类中需要用到ServerSocketFactory类来创建SSLServerSocket类实例，然后再通过SSLServerSocket来获取SSLSocket实例，这里考虑到面向对象中的面向接口编程的理念，所以代码中并没有出现SSLServerSocket和SSLSocket，而是用了他们的父类ServerSocket和Socket。在获取到ServerSocket和Socket实例以后，剩下的代码就和不使用加密方式一样了。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SSLServer</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 服务器端授权的用户名和密码</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String USER_NAME = <span class="string">"principal"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String PASSWORD = <span class="string">"credential"</span>;</span><br><span class="line">    <span class="comment">// 服务器端保密内容</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String SECRET_CONTENT =</span><br><span class="line">            <span class="string">"This is confidential content from server X, for your eye!"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> SSLServerSocket serverSocket = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">SSLServer</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="comment">// 通过套接字工厂，获取一个服务器端套接字</span></span><br><span class="line">        SSLServerSocketFactory socketFactory = (SSLServerSocketFactory)</span><br><span class="line">                SSLServerSocketFactory.getDefault();</span><br><span class="line">        serverSocket = (SSLServerSocket) socketFactory.createServerSocket(<span class="number">7070</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">runServer</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                System.out.println(<span class="string">"Waiting for connection..."</span>);</span><br><span class="line">                <span class="comment">// 服务器端套接字进入阻塞状态，等待来自客户端的连接请求</span></span><br><span class="line">                SSLSocket socket = (SSLSocket) serverSocket.accept();</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 获取服务器端套接字输入流</span></span><br><span class="line">                BufferedReader input = <span class="keyword">new</span> BufferedReader(</span><br><span class="line">                        <span class="keyword">new</span> InputStreamReader(socket.getInputStream()));</span><br><span class="line">                <span class="comment">// 从输入流中读取客户端用户名和密码</span></span><br><span class="line">                String userName = input.readLine();</span><br><span class="line">                String password = input.readLine();</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 获取服务器端套接字输出流</span></span><br><span class="line">                PrintWriter output = <span class="keyword">new</span> PrintWriter(</span><br><span class="line">                        <span class="keyword">new</span> OutputStreamWriter(socket.getOutputStream()));</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 对请求进行认证，如果通过则将保密内容发送给客户端</span></span><br><span class="line">                <span class="keyword">if</span> (userName.equals(USER_NAME) &amp;&amp; password.equals(PASSWORD)) &#123;</span><br><span class="line">                    output.println(<span class="string">"Welcome, "</span> + userName);</span><br><span class="line">                    output.println(SECRET_CONTENT);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    output.println(<span class="string">"Authentication failed, you have no access to server X..."</span>);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 关闭流资源和套接字资源</span></span><br><span class="line">                output.close();</span><br><span class="line">                input.close();</span><br><span class="line">                socket.close();</span><br><span class="line"></span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException ioException) &#123;</span><br><span class="line">                ioException.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String args[])</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        SSLServer server = <span class="keyword">new</span> SSLServer();</span><br><span class="line">        server.runServer();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>　　客户端则需要采用SSLSocketFactory生成一个Socket，示例如下：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SSLClient</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> SSLSocket socket = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">SSLClient</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        <span class="comment">// 通过套接字工厂，获取一个客户端套接字</span></span><br><span class="line">        SSLSocketFactory socketFactory = (SSLSocketFactory)</span><br><span class="line">                SSLSocketFactory.getDefault();</span><br><span class="line">        socket = (SSLSocket) socketFactory.createSocket(<span class="string">"localhost"</span>, <span class="number">7070</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">connect</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 获取客户端套接字输出流</span></span><br><span class="line">            PrintWriter output = <span class="keyword">new</span> PrintWriter(</span><br><span class="line">                    <span class="keyword">new</span> OutputStreamWriter(socket.getOutputStream()));</span><br><span class="line">            <span class="comment">// 将用户名和密码通过输出流发送到服务器端</span></span><br><span class="line">            String userName = <span class="string">"principal"</span>;</span><br><span class="line">            output.println(userName);</span><br><span class="line">            String password = <span class="string">"credential"</span>;</span><br><span class="line">            output.println(password);</span><br><span class="line">            output.flush();</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 获取客户端套接字输入流</span></span><br><span class="line">            BufferedReader input = <span class="keyword">new</span> BufferedReader(</span><br><span class="line">                    <span class="keyword">new</span> InputStreamReader(socket.getInputStream()));</span><br><span class="line">            <span class="comment">// 从输入流中读取服务器端传送的数据内容，并打印出来</span></span><br><span class="line">            String response = input.readLine();</span><br><span class="line">            response += <span class="string">"\n "</span> + input.readLine();</span><br><span class="line">            System.out.println(response);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 关闭流资源和套接字资源</span></span><br><span class="line">            output.close();</span><br><span class="line">            input.close();</span><br><span class="line">            socket.close();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException ioException) &#123;</span><br><span class="line">            ioException.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            System.exit(<span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String args[])</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        <span class="keyword">new</span> SSLClient().connect();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>　　此时我们运行客户端和服务端代码，两端都会出现错误，从下面的关键错误可以看到： SSL 在握手阶段双方没有能够协商出加密方法等信息。这是因为默认情况下，java 虚拟机没有与 SSL 相关的配置，需要开发者自己按照文档进行一些配置。<br>　　1. 服务器端输出：javax.net.ssl.SSLHandshakeException: no cipher suites in common<br>　　2. 客户端输出：javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure<br>　　在 JDK 中提供了一个安全钥匙与证书的管理工具 Keytool。Keytool <strong>把钥匙，证书以及和与它们相关联的证书链储存到一个 keystore </strong>中，默任的实现中 keystore 的是一个文件，<strong>keystore本身有一个访问密码来保护存储在其中的内容</strong>。就本样例程序而言，只需要配置客户端和服务器端双方信任就可以了。可以按照如下几步来完成：</p>
<p>　　1. 进入本地的 java 安装位置的 bin 目录中 cd /java/bin<br>　　2. 创建一个客户端 keystore 文件，按提示输入信息即可<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">keytool -genkey -alias sslclient -keystore sslclientkeys</span><br></pre></td></tr></table></figure></p>
<p>　　3. 将客户端 keystore 文件导出成证书格式<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">keytool -export -alias sslclient -keystore sslclientkeys -file sslclient.cer</span><br></pre></td></tr></table></figure></p>
<p>　　4. 创建一个服务器端 keystore 文件<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">keytool -genkey -alias sslserver -keystore sslserverkeys</span><br></pre></td></tr></table></figure></p>
<p>　　5. 将服务器端 keystore 文件导出成证书格式<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">keytool -export -alias sslserver -keystore sslserverkeys -file sslserver.cer</span><br></pre></td></tr></table></figure></p>
<p>　　6. 将客户端证书导入到服务器端受信任的 keystore 中<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">keytool -import -alias sslclient -keystore sslservertrust -file sslclient.cer</span><br></pre></td></tr></table></figure></p>
<p>　　7. 将服务器端证书导入到客户端受信任的 keystore 中<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">keytool -import -alias sslserver -keystore sslclienttrust -file sslserver.cer</span><br></pre></td></tr></table></figure></p>
<p>　　8. 以上所有步骤都完成后，还可以通过命令来查看 keystore 文件基本信息<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">keytool -list -keystore sslclienttrust</span><br></pre></td></tr></table></figure></p>
<p>　　将前面创建的所有 keystore 文件从 java 的 bin 目录中剪切出来，移动到样例程序的执行目录中，通过运行程序时候的系统属性来指定这些文件，重新执行一遍样例程序。</p>
<p>  java -cp ./build/classes<br>      -Djavax.net.ssl.keyStore=sslserverkeys<br>      -Djavax.net.ssl.keyStorePassword=123456<br>      -Djavax.net.ssl.trustStore=sslservertrust<br>      -Djavax.net.ssl.trustStorePassword=123456<br>  example.ssl.codes.SSLServer </p>
<p>  java -cp ./build/classes<br>      -Djavax.net.ssl.keyStore=sslclientkeys<br>      -Djavax.net.ssl.keyStorePassword=123456<br>      -Djavax.net.ssl.trustStore=sslclienttrust<br>      -Djavax.net.ssl.trustStorePassword=123456<br>  example.ssl.codes.SSLClient</p>
<p>　　执行后，客户端与服务器端成功建立起 SSL 的连接，然后服务器端成功将字符串发送给客户端，客户端将其打印出来。客户端输出如下：<br>Welcome, principal<br> This is confidential content from server X, for your eye!<br>　　上面的示例完成了双向认证的过程，下面简单说一下单项认证的过程：<br>　　1. 终端进入%JAVA_HOME%\bin目录，运行如下：keytool.exe -genkey -alias mysocket -keyalg RSA -keystore mysocket.jks<br>　　2. 在提示输入项中，密码项自己给定，其它都不改直接回车，这里使用的密码是“mysocket”。生成的文件mysocket.jks也在目录%JAVA_HOME%\bin中<br>　　3. 将mysocket.jks拷贝到指定目录，运行Server<br>java -Djavax.net.ssl.keyStore=mysocket.jks -Djavax.net.ssl.keyStorePassword=mysocket demo5.MyServer<br>　　4. 运行Client<br>java -Djavax.net.ssl.trustStore=mysocket.jks  -Djavax.net.ssl.trustStorePassword=mysocket demo5.MyClient<br>　　5. 查看Server和Client的输出<br>　　这里我们没有导出服务端的keystore到证书文件，也没有将证书文件导入到客户端受信任的证书中。也没有导出客户端的keystore到证书文件，也没有将证书文件导入到服务端受信任的证书中。</p>
<h2 id="u4F18_u5316"><a href="#u4F18_u5316" class="headerlink" title="优化"></a>优化</h2><p>　　上面的代码在启动时，都需要-D参数，指定keystore和受信任证书的文件和密码。下面介绍如何在代码中实现该功能，特别是需要其他类型的证书支持时！实际的应用可以参考apple的apns服务。下面的实现主要代码为<a href="http://blog.csdn.net/a19881029/article/details/11742361" target="_blank" rel="external">http://blog.csdn.net/a19881029/article/details/11742361</a>中实现。只是在实验过程中发现存在数据掉包的问题，所以将其DataInputStream和DateOutputStream替换成了ObjectInputStream和ObjectOutputStream。请读者终端关注代码中的注释，加深理解</p>
<h3 id="u8BC1_u4E66"><a href="#u8BC1_u4E66" class="headerlink" title="证书"></a>证书</h3><p>　　首先我们生成需要的证书，如下：<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#创建服务端keystore 私钥</span></span><br><span class="line">keytool -genkey -keystore .\certificate\server.jks -storepass <span class="number">1234</span>sp -keyalg RSA -keypass <span class="number">1234</span>kp</span><br><span class="line"><span class="comment">#创建客户端keystore 私钥</span></span><br><span class="line">keytool -genkey -keystore .\certificate\client.jks -storepass <span class="number">1234</span>sp -keyalg RSA -keypass <span class="number">1234</span>kp</span><br><span class="line"><span class="comment">#导出服务端证书 公钥</span></span><br><span class="line">keytool -export -keystore .\certificate\server.jks -storepass <span class="number">1234</span>sp -file .\certificate\server.cer</span><br><span class="line"><span class="comment">#导出客户端证书  公钥</span></span><br><span class="line">keytool -export -keystore .\certificate\client.jks -storepass <span class="number">1234</span>sp -file .\certificate\client.cer</span><br><span class="line"><span class="comment">#将服务端证书导入到客户端clientTrust，即服务端公钥告诉给了客户端</span></span><br><span class="line">keytool -import -keystore .\certificate\clientTrust.jks -storepass <span class="number">1234</span>sp -file .\certificate\server.cer</span><br><span class="line"><span class="comment">#将客户端证书导入到服务端serverTrust，即客户端公钥告诉给了服务端</span></span><br><span class="line">keytool -import -keystore .\certificate\serverTrust.jks -storepass <span class="number">1234</span>sp -file .\certificate\client.cer</span><br></pre></td></tr></table></figure></p>
<h3 id="u670D_u52A1_u7AEF_u8BA4_u8BC1"><a href="#u670D_u52A1_u7AEF_u8BA4_u8BC1" class="headerlink" title="服务端认证"></a>服务端认证</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Auth</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> SSLContext sslContext;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> SSLContext <span class="title">getSSLContext</span><span class="params">()</span> <span class="keyword">throws</span> Exception</span>&#123;</span><br><span class="line">        Properties p = Configuration.getConfig();</span><br><span class="line">        String protocol = p.getProperty(<span class="string">"protocol"</span>);</span><br><span class="line">        String serverCer = p.getProperty(<span class="string">"serverCer"</span>);</span><br><span class="line">        String serverCerPwd = p.getProperty(<span class="string">"serverCerPwd"</span>);</span><br><span class="line">        String serverKeyPwd = p.getProperty(<span class="string">"serverKeyPwd"</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//加载服务端keystore</span></span><br><span class="line">        KeyStore keyStore = KeyStore.getInstance(<span class="string">"JKS"</span>);</span><br><span class="line">        keyStore.load(Auth.class.getResourceAsStream(serverCer),</span><br><span class="line">                serverCerPwd.toCharArray());</span><br><span class="line"></span><br><span class="line">        <span class="comment">//获取keystore中所有key链</span></span><br><span class="line">        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(<span class="string">"SunX509"</span>);</span><br><span class="line">        keyManagerFactory.init(keyStore, serverKeyPwd.toCharArray());</span><br><span class="line">        KeyManager[] kms = keyManagerFactory.getKeyManagers();</span><br><span class="line"></span><br><span class="line">        TrustManager[] tms = <span class="keyword">null</span>;</span><br><span class="line">        <span class="comment">// 如果采用双向认证（服务端也需要认证客户端），</span></span><br><span class="line">        <span class="comment">// 服务端则需要加载服务端受信任证书（客户端公钥已写入服务端务端受信任证书）</span></span><br><span class="line">        <span class="keyword">if</span>(Configuration.getConfig().getProperty(<span class="string">"authority"</span>).equals(<span class="string">"2"</span>))&#123;</span><br><span class="line">            String serverTrustCer = p.getProperty(<span class="string">"serverTrustCer"</span>);</span><br><span class="line">            String serverTrustCerPwd = p.getProperty(<span class="string">"serverTrustCerPwd"</span>);</span><br><span class="line"></span><br><span class="line">            <span class="comment">//加载受信任证书的Key Store</span></span><br><span class="line">            keyStore = KeyStore.getInstance(<span class="string">"JKS"</span>);</span><br><span class="line">            keyStore.load(Auth.class.getResourceAsStream(serverTrustCer),</span><br><span class="line">                    serverTrustCerPwd.toCharArray());</span><br><span class="line">            <span class="comment">//从keystrore中获取受信任证书链</span></span><br><span class="line">            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(<span class="string">"SunX509"</span>);</span><br><span class="line">            trustManagerFactory.init(keyStore);</span><br><span class="line">            tms = trustManagerFactory.getTrustManagers();</span><br><span class="line">        &#125;</span><br><span class="line">        sslContext = SSLContext.getInstance(protocol);</span><br><span class="line">        <span class="comment">// 第一个参数为服务端所有私钥</span></span><br><span class="line">        <span class="comment">// 第二个参数为客户端所有公钥（双向认证），为空则表示单项认证（客户端验证服务端）</span></span><br><span class="line">        sslContext.init(kms, tms, <span class="keyword">null</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> sslContext;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="u670D_u52A1_u7AEF_u4E3B_u7A0B_u5E8F"><a href="#u670D_u52A1_u7AEF_u4E3B_u7A0B_u5E8F" class="headerlink" title="服务端主程序"></a>服务端主程序</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Server</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> SSLContext sslContext;</span><br><span class="line">    <span class="keyword">private</span> SSLServerSocketFactory sslServerSocketFactory;</span><br><span class="line">    <span class="keyword">private</span> SSLServerSocket sslServerSocket;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Executor executor;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Server</span><span class="params">()</span> <span class="keyword">throws</span> Exception</span>&#123;</span><br><span class="line">        Properties p = Configuration.getConfig();</span><br><span class="line">        Integer serverListenPort = Integer.valueOf(p.getProperty(<span class="string">"serverListenPort"</span>));</span><br><span class="line">        Integer serverThreadPoolSize = Integer.valueOf(p.getProperty(<span class="string">"serverThreadPoolSize"</span>));</span><br><span class="line">        Integer serverRequestQueueSize = Integer.valueOf(p.getProperty(<span class="string">"serverRequestQueueSize"</span>));</span><br><span class="line">        Integer authority = Integer.valueOf(p.getProperty(<span class="string">"authority"</span>));</span><br><span class="line"></span><br><span class="line">        executor = Executors.newFixedThreadPool(serverThreadPoolSize);</span><br><span class="line"></span><br><span class="line">        sslContext = Auth.getSSLContext();</span><br><span class="line">        sslServerSocketFactory = sslContext.getServerSocketFactory();</span><br><span class="line">        <span class="comment">//只是创建一个TCP连接，SSL handshake还没开始</span></span><br><span class="line">        <span class="comment">//客户端或服务端第一次试图获取socket输入流或输出流时，</span></span><br><span class="line">        <span class="comment">//SSL handshake才会开始</span></span><br><span class="line">        <span class="comment">//加密套件（ciphersuits）包括一组加密参数，这些参数指定了加密算法、密钥长度等加密等信息。</span></span><br><span class="line">        sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket();</span><br><span class="line">        String[] pwdsuits = sslServerSocket.getSupportedCipherSuites();</span><br><span class="line">        sslServerSocket.setEnabledCipherSuites(pwdsuits);</span><br><span class="line">        <span class="comment">//默认是client mode，必须在握手开始之前调用</span></span><br><span class="line">        sslServerSocket.setUseClientMode(<span class="keyword">false</span>);</span><br><span class="line">        <span class="keyword">if</span>(authority.intValue() == <span class="number">2</span>)&#123;</span><br><span class="line">            <span class="comment">//只有设置为server mode，该配置才生效</span></span><br><span class="line">            <span class="comment">//如果客户端不提供其证书，通信将会结束</span></span><br><span class="line">            sslServerSocket.setNeedClientAuth(<span class="keyword">true</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="comment">//只有设置为server mode，该配置才生效</span></span><br><span class="line">            <span class="comment">//即使客户端不提供其证书，通信也将继续</span></span><br><span class="line">            sslServerSocket.setWantClientAuth(<span class="keyword">true</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        sslServerSocket.setReuseAddress(<span class="keyword">true</span>);</span><br><span class="line">        sslServerSocket.setReceiveBufferSize(<span class="number">128</span>*<span class="number">1024</span>);</span><br><span class="line">        <span class="comment">//空方法</span></span><br><span class="line">        sslServerSocket.setPerformancePreferences(<span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>);</span><br><span class="line">        <span class="comment">//第二个参数为backlog</span></span><br><span class="line">        sslServerSocket.bind(<span class="keyword">new</span> InetSocketAddress(serverListenPort), serverRequestQueueSize);</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">"Server start up!"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">service</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">while</span>(<span class="keyword">true</span>)&#123;</span><br><span class="line">            SSLSocket socket = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">try</span>&#123;</span><br><span class="line">                System.out.println(<span class="string">"Wait for client request!"</span>);</span><br><span class="line">                socket = (SSLSocket)sslServerSocket.accept();</span><br><span class="line">                System.out.println(<span class="string">"Get client request!"</span>);</span><br><span class="line">                socket.setTcpNoDelay(<span class="keyword">true</span>);</span><br><span class="line">                <span class="comment">//新的连接建立，处理则放到线程池中，避免阻塞</span></span><br><span class="line">                Runnable job = <span class="keyword">new</span> Job(socket);</span><br><span class="line">                executor.execute(job);</span><br><span class="line">            &#125;<span class="keyword">catch</span>(Exception e)&#123;</span><br><span class="line">                System.out.println(<span class="string">"server run exception"</span>);</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    socket.close();</span><br><span class="line">                &#125; <span class="keyword">catch</span> (IOException e1) &#123;</span><br><span class="line">                    e1.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        Server server;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            server = <span class="keyword">new</span> Server();</span><br><span class="line">            server.service();</span><br><span class="line">        &#125;<span class="keyword">catch</span>(Exception e)&#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">            System.out.println(<span class="string">"server socket establish error!"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="u6570_u636E_u5904_u7406"><a href="#u6570_u636E_u5904_u7406" class="headerlink" title="数据处理"></a>数据处理</h3><p>　　在一个新的线程中处理客户端发来的数据<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Job</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> Socket socket;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Job</span><span class="params">(Socket socket)</span></span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.socket = socket;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            ObjectInputStream ois = SocketIO.getObjectInput(socket);</span><br><span class="line">            String user = ois.readUTF();</span><br><span class="line">            <span class="keyword">int</span> pwd = ois.readInt();</span><br><span class="line">            System.out.println(<span class="string">"request user:"</span> + user);</span><br><span class="line">            System.out.println(<span class="string">"request pwd:"</span> + pwd);</span><br><span class="line"></span><br><span class="line">            String result = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">if</span>(<span class="keyword">null</span> != user &amp;&amp; !user.equals(<span class="string">""</span>) &amp;&amp; user.equals(<span class="string">"name"</span>) &amp;&amp; pwd == <span class="number">123</span>)&#123;</span><br><span class="line">                result = <span class="string">"login success"</span>;</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                result = <span class="string">"login failed"</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            ObjectOutputStream oos = SocketIO.getObjectOutput(socket);</span><br><span class="line">            oos.writeUTF(result);</span><br><span class="line">            oos.flush();</span><br><span class="line">            System.out.println(<span class="string">"response info:"</span> + result);</span><br><span class="line">        &#125;<span class="keyword">catch</span>(Exception e)&#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">            System.out.println(<span class="string">"business thread run exception"</span>);</span><br><span class="line">        &#125;<span class="keyword">finally</span>&#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                socket.close();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">                System.out.println(<span class="string">"server socket close error"</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h3 id="u5BA2_u6237_u7AEF_u8BA4_u8BC1"><a href="#u5BA2_u6237_u7AEF_u8BA4_u8BC1" class="headerlink" title="客户端认证"></a>客户端认证</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Auth</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> SSLContext sslContext;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> SSLContext <span class="title">getSSLContext</span><span class="params">()</span> <span class="keyword">throws</span> Exception</span>&#123;</span><br><span class="line">        Properties p = Configuration.getConfig();</span><br><span class="line">        String protocol = p.getProperty(<span class="string">"protocol"</span>);</span><br><span class="line">        String clientTrustCerFile = p.getProperty(<span class="string">"clientTrustCer"</span>);</span><br><span class="line">        String clientTrustCerPwd = p.getProperty(<span class="string">"clientTrustCerPwd"</span>);</span><br><span class="line"></span><br><span class="line">        KeyStore keyStore = KeyStore.getInstance(<span class="string">"JKS"</span>);</span><br><span class="line">        <span class="comment">// 不管是单向认证，还是双向认证，客户端都需要验证服务端</span></span><br><span class="line">        <span class="comment">// 此时客户端则需要加载客户端受信任证书（信任哪些服务器）</span></span><br><span class="line">        <span class="comment">// 该证书中包含有服务端的公钥（可以这么理解）</span></span><br><span class="line">        keyStore.load(Auth.class.getResourceAsStream(clientTrustCerFile),</span><br><span class="line">                clientTrustCerPwd.toCharArray());</span><br><span class="line"></span><br><span class="line">        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(<span class="string">"SunX509"</span>);</span><br><span class="line">        trustManagerFactory.init(keyStore);</span><br><span class="line">        TrustManager[] tms = trustManagerFactory.getTrustManagers();</span><br><span class="line"></span><br><span class="line">        KeyManager[] kms = <span class="keyword">null</span>;</span><br><span class="line">        <span class="comment">// 双线认证，则需要服务端验证客户端的证书</span></span><br><span class="line">        <span class="comment">// 服务端读取器其受信任证书（信任哪些客户端，可以理解为证书中含有客户端公钥）</span></span><br><span class="line">        <span class="comment">// 服务端加载客户端公钥，客户端则需要加载客户端私钥</span></span><br><span class="line">        <span class="keyword">if</span>(Configuration.getConfig().getProperty(<span class="string">"authority"</span>).equals(<span class="string">"2"</span>))&#123;</span><br><span class="line">            String clientCerFile = p.getProperty(<span class="string">"clientCer"</span>);</span><br><span class="line">            String clientCerPwd = p.getProperty(<span class="string">"clientCerPwd"</span>);</span><br><span class="line">            String clientKeyPwd = p.getProperty(<span class="string">"clientKeyPwd"</span>);</span><br><span class="line"></span><br><span class="line">            keyStore = KeyStore.getInstance(<span class="string">"JKS"</span>);</span><br><span class="line">            keyStore.load(Auth.class.getResourceAsStream(clientCerFile),</span><br><span class="line">                    clientCerPwd.toCharArray());</span><br><span class="line"></span><br><span class="line">            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(<span class="string">"SunX509"</span>);</span><br><span class="line">            keyManagerFactory.init(keyStore, clientKeyPwd.toCharArray());</span><br><span class="line">            kms = keyManagerFactory.getKeyManagers();</span><br><span class="line">        &#125;</span><br><span class="line">        sslContext = SSLContext.getInstance(protocol);</span><br><span class="line">        sslContext.init(kms, tms, <span class="keyword">null</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> sslContext;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="u5BA2_u6237_u7AEF_u4E3B_u7A0B_u5E8F"><a href="#u5BA2_u6237_u7AEF_u4E3B_u7A0B_u5E8F" class="headerlink" title="客户端主程序"></a>客户端主程序</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Client</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> SSLContext sslContext;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> port = <span class="number">10000</span>;</span><br><span class="line">    <span class="keyword">private</span> String host = <span class="string">"127.0.0.1"</span>;</span><br><span class="line">    <span class="keyword">private</span> SSLSocket socket;</span><br><span class="line">    <span class="keyword">private</span> Properties p;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Client</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            p = Configuration.getConfig();</span><br><span class="line"></span><br><span class="line">            sslContext = Auth.getSSLContext();</span><br><span class="line">            SSLSocketFactory factory = sslContext.getSocketFactory();</span><br><span class="line">            socket = (SSLSocket) factory.createSocket();</span><br><span class="line">            String[] pwdsuits = socket.getSupportedCipherSuites();</span><br><span class="line">            <span class="comment">//socket可以使用所有支持的加密套件</span></span><br><span class="line">            <span class="comment">//加密套件（ciphersuits）包括一组加密参数，这些参数指定了加密算法、密钥长度等加密等信息。</span></span><br><span class="line">            socket.setEnabledCipherSuites(pwdsuits);</span><br><span class="line">            <span class="comment">//默认就是true</span></span><br><span class="line">            socket.setUseClientMode(<span class="keyword">true</span>);</span><br><span class="line">            socket.setTcpNoDelay(<span class="keyword">true</span>);</span><br><span class="line">            SocketAddress address = <span class="keyword">new</span> InetSocketAddress(host, port);</span><br><span class="line">            socket.connect(address, <span class="number">0</span>);</span><br><span class="line">            <span class="comment">//握手完成监听器</span></span><br><span class="line">            MyHandshakeCompletedListener listener = <span class="keyword">new</span> MyHandshakeCompletedListener();</span><br><span class="line">            socket.addHandshakeCompletedListener(listener);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">            System.out.println(<span class="string">"socket establish failed!"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">request</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            String user = <span class="string">"name"</span>;</span><br><span class="line">            <span class="keyword">int</span> pwd = <span class="number">123</span>;</span><br><span class="line">            ObjectOutputStream oos = SocketIO.getObjectOutput(socket);</span><br><span class="line">            oos.writeUTF(user);</span><br><span class="line">            oos.writeInt(pwd);</span><br><span class="line">            oos.flush();</span><br><span class="line"></span><br><span class="line">            ObjectInputStream ois = SocketIO.getObjectInput(socket);</span><br><span class="line">            String receive = ois.readUTF();</span><br><span class="line">            System.out.println(<span class="string">"request result:"</span> + receive);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">            System.out.println(<span class="string">"request error"</span>);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                socket.close();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        Client client = <span class="keyword">new</span> Client();</span><br><span class="line">        client.request();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="u5DE5_u5177_u7C7B"><a href="#u5DE5_u5177_u7C7B" class="headerlink" title="工具类"></a>工具类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Configuration</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Properties config;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Properties <span class="title">getConfig</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            <span class="keyword">if</span>(<span class="keyword">null</span> == config)&#123;</span><br><span class="line">                InputStream input = Configuration.class.getResourceAsStream(<span class="string">"/conf/conf.properties"</span>);</span><br><span class="line">                config = <span class="keyword">new</span> Properties();</span><br><span class="line">                config.load(input);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;<span class="keyword">catch</span>(Exception e)&#123;</span><br><span class="line">            config = <span class="keyword">new</span> Properties();</span><br><span class="line">            config.setProperty(<span class="string">"protocol"</span>, <span class="string">"TLSV1"</span>);</span><br><span class="line">            config.setProperty(<span class="string">"serverCer"</span>, <span class="string">"/certificate/server.jks"</span>);</span><br><span class="line">            config.setProperty(<span class="string">"serverCerPwd"</span>, <span class="string">"1234sp"</span>);</span><br><span class="line">            config.setProperty(<span class="string">"serverKeyPwd"</span>, <span class="string">"1234kp"</span>);</span><br><span class="line">            config.setProperty(<span class="string">"serverTrustCer"</span>, <span class="string">"/certificate/serverTrust.jks"</span>);</span><br><span class="line">            config.setProperty(<span class="string">"serverTrustCerPwd"</span>, <span class="string">"1234sp"</span>);</span><br><span class="line">            config.setProperty(<span class="string">"clientCer"</span>, <span class="string">"/certificate/client.jks"</span>);</span><br><span class="line">            config.setProperty(<span class="string">"clientCerPwd"</span>, <span class="string">"1234sp"</span>);</span><br><span class="line">            config.setProperty(<span class="string">"clientKeyPwd"</span>, <span class="string">"1234kp"</span>);</span><br><span class="line">            config.setProperty(<span class="string">"clientTrustCer"</span>, <span class="string">"/certificate/clientTrust.jks"</span>);</span><br><span class="line">            config.setProperty(<span class="string">"clientTrustCerPwd"</span>, <span class="string">"1234sp"</span>);</span><br><span class="line">            config.setProperty(<span class="string">"serverListenPort"</span>, <span class="string">"10000"</span>);</span><br><span class="line">            config.setProperty(<span class="string">"serverThreadPoolSize"</span>, <span class="string">"5"</span>);</span><br><span class="line">            config.setProperty(<span class="string">"serverRequestQueueSize"</span>, <span class="string">"10"</span>);</span><br><span class="line">            config.setProperty(<span class="string">"socketStreamEncoding"</span>, <span class="string">"UTF-8"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> config;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyHandshakeCompletedListener</span> <span class="keyword">implements</span> <span class="title">HandshakeCompletedListener</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handshakeCompleted</span><span class="params">(HandshakeCompletedEvent arg0)</span> </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"Handshake finished successfully"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SocketIO</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ObjectInputStream <span class="title">getObjectInput</span><span class="params">(Socket socket)</span> <span class="keyword">throws</span> IOException</span>&#123;</span><br><span class="line">        ObjectInputStream ois = <span class="keyword">new</span> ObjectInputStream(socket.getInputStream());</span><br><span class="line">        <span class="keyword">return</span> ois;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ObjectOutputStream <span class="title">getObjectOutput</span><span class="params">(Socket socket)</span> <span class="keyword">throws</span> IOException</span>&#123;</span><br><span class="line">        ObjectOutputStream oos = <span class="keyword">new</span> ObjectOutputStream(socket.getOutputStream());</span><br><span class="line">        <span class="keyword">return</span> oos;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="u603B_u7ED3"><a href="#u603B_u7ED3" class="headerlink" title="总结"></a>总结</h2><p>　　到这里我们也就完成了SSL通信的讲解，事实上，这里讲解的主要是socket，如何实现NIO的呢，请读者思考，建议大家参考一下netty中的实现。简单点说SSL通信，其实无非下面几点：<br>　　1. 服务端加载其私钥，客户端加载服务端公钥（服务端证书导出到客户端受信任的证书中，则形成公钥）<br>　　3. 如果为双向通信，则还需要客户端加载其私钥，服务端加载客户端公钥。<br>　　再次强调一下，请读者重点关注程序中的注释，方便理解。完整源码请下载：<a href="/code/ssl-socket.zip">ssl-socket.zip</a></p>
<h2 id="u53C2_u8003_u8D44_u6599"><a href="#u53C2_u8003_u8D44_u6599" class="headerlink" title="参考资料"></a>参考资料</h2><p><a href="http://www.ibm.com/developerworks/cn/java/j-lo-ssltls/" target="_blank" rel="external">http://www.ibm.com/developerworks/cn/java/j-lo-ssltls/</a><br><a href="http://blog.csdn.net/a19881029/article/details/11742361" target="_blank" rel="external">http://blog.csdn.net/a19881029/article/details/11742361</a></p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2015/12/23/nio/2.SSL Socket/" data-id="cijzzpv7000581cfsx7yaoj7s" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/java/">java</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/socket/">socket</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-nio/1.Socket入门" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2015/12/23/nio/1.Socket入门/" class="article-date">
  <time datetime="2015-12-22T17:01:00.000Z" itemprop="datePublished">2015-12-23</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/nio/">nio</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2015/12/23/nio/1.Socket入门/">Socket入门</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="u7B80_u4ECB"><a href="#u7B80_u4ECB" class="headerlink" title="简介"></a>简介</h2><p>　　在客户/服务器通信模式中，客户端需要主动创建与服务器连接的 Socket(套接字)，服务器端收到了客户端的连接请求后，也会创建与客户连接的 Socket。Socket可看做是通信连接两端的收发器，服务器与客户端都通过 Socket 来收发数据。</p>
<h2 id="u5B9E_u4F8B"><a href="#u5B9E_u4F8B" class="headerlink" title="实例"></a>实例</h2><p>　　我们直接从网上摘录一个通过Socket进行客户端与服务器端通信的示例来讲解，首先贴出服务器端的代码<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> demo1;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyServer</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        ServerSocket server = <span class="keyword">new</span> ServerSocket(<span class="number">9999</span>);</span><br><span class="line">        <span class="comment">//服务端启动后，阻塞在此次，至到客户端连接</span></span><br><span class="line">        Socket socket = server.accept();</span><br><span class="line">        BufferedReader in = <span class="keyword">new</span> BufferedReader(<span class="keyword">new</span> InputStreamReader(socket.getInputStream()));</span><br><span class="line">        PrintWriter out = <span class="keyword">new</span> PrintWriter(socket.getOutputStream());</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">            String msg = in.readLine();</span><br><span class="line">            System.out.println(msg);</span><br><span class="line">            <span class="keyword">if</span> (msg.equals(<span class="string">"bye"</span>)) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            out.println(<span class="string">"Server received "</span> + msg);</span><br><span class="line">            out.flush();</span><br><span class="line">        &#125;</span><br><span class="line">        socket.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>　　这里我们构造了一个ServerSocket对象，监听9999端口。有客户端连接之后，创建了一个Socket对象，这里印证了上面所说的。服务端每次收到客户端的数据后，向客户端发送已收到数据的消息。下面贴出客户端连接的代码。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> demo1;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyClient</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Socket socket = <span class="keyword">new</span> Socket(<span class="string">"localhost"</span>, <span class="number">9999</span>);</span><br><span class="line">        BufferedReader socketInput = <span class="keyword">new</span> BufferedReader(<span class="keyword">new</span> InputStreamReader(socket.getInputStream()));</span><br><span class="line">        PrintWriter out = <span class="keyword">new</span> PrintWriter(socket.getOutputStream());</span><br><span class="line">        BufferedReader reader = <span class="keyword">new</span> BufferedReader(<span class="keyword">new</span> InputStreamReader(System.in));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">            String msg = reader.readLine();</span><br><span class="line">            out.println(msg);</span><br><span class="line">            out.flush();</span><br><span class="line">            <span class="keyword">if</span> (msg.equals(<span class="string">"bye"</span>)) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//客户端发送数据后，等待socket输入（服务端发送过来的数据）</span></span><br><span class="line">            System.out.println(socketInput.readLine());</span><br><span class="line">        &#125;</span><br><span class="line">        socket.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>　　这里我们构造了一个Socket对象，连接到9999端口。它读取控制台输入的数据，发送到服务器端，如果输入为bye，则退出程序。</p>
<h2 id="u591A_u7EBF_u7A0B"><a href="#u591A_u7EBF_u7A0B" class="headerlink" title="多线程"></a>多线程</h2><p>　　这里我们完成了一个最基本的客户端和服务端通信的例子。其实，这个<font color="red">程序在多用户连接的情况下是存在问题的</font> ，具体表现为：启动多个客户端之后，只有第一个启动的客户端可以收发消息。其他的客户端也能连接（关闭客户端后也会引起服务端的关闭），但是不能收发消息，也不会报错。这是为什么呢？<br>　　ServerSocket的accept方法是一个阻塞的方法，每次创建新的客户端后，该方法才会返回，程序继续执行后面的操作。如果新的客户端来了，此时程序需要重新运行到accept方法出才能保证正常的逻辑。但是该示例中accept方法永远只会运行一次，故只有第一个客户端的连接有效。下面我们对服务端程序进行如下改造，使其同时支持多个客户端的连接。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyServer</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        ServerSocket server = <span class="keyword">new</span> ServerSocket(<span class="number">10000</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">            Socket socket = server.accept();</span><br><span class="line">            invoke(socket);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">invoke</span><span class="params">(<span class="keyword">final</span> Socket client)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                BufferedReader in = <span class="keyword">null</span>;</span><br><span class="line">                PrintWriter out = <span class="keyword">null</span>;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    in = <span class="keyword">new</span> BufferedReader(<span class="keyword">new</span> InputStreamReader(client.getInputStream()));</span><br><span class="line">                    out = <span class="keyword">new</span> PrintWriter(client.getOutputStream());</span><br><span class="line"></span><br><span class="line">                    <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">                        String msg = in.readLine();</span><br><span class="line">                        System.out.println(msg);</span><br><span class="line">                        out.println(<span class="string">"Server received "</span> + msg);</span><br><span class="line">                        out.flush();</span><br><span class="line">                        <span class="keyword">if</span> (msg.equals(<span class="string">"bye"</span>)) &#123;</span><br><span class="line">                            <span class="keyword">break</span>;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">catch</span>(IOException ex) &#123;</span><br><span class="line">                    ex.printStackTrace();</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        in.close();</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (Exception e) &#123;&#125;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        out.close();</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (Exception e) &#123;&#125;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        client.close();</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (Exception e) &#123;&#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>　　这个程序解决了第一个示例的问题，但是存在一个问题就是每次有新的客户端连接时，都需要创建一个新的线程，使其支持该客户端的通信。在客户端较多的时候，特别是移动端的情况下，这样会给服务器带来很大的压力。</p>
<h2 id="u4F20_u8F93_u5BF9_u8C61"><a href="#u4F20_u8F93_u5BF9_u8C61" class="headerlink" title="传输对象"></a>传输对象</h2><p>　　上面的例子介绍了怎样建立Java Socket通信，这里说一下怎样使用Java Socket传输对象。要传输对象，首先需要一个普通的对象类，由于需要序列化这个对象以便在网络上传输，所以实现java.io.Serializable接口就是必不可少的了。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">User</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">User</span><span class="params">(String name, String password)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">        <span class="keyword">this</span>.password = password;</span><br><span class="line">    &#125;</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>　　下面我们直接列出改造过后的服务端和客户端代码。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyServer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> Logger logger = Logger.getLogger(MyServer.class.getName());</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        ServerSocket server = <span class="keyword">new</span> ServerSocket(<span class="number">10000</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">            Socket socket = server.accept();</span><br><span class="line">            invoke(socket);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">invoke</span><span class="params">(<span class="keyword">final</span> Socket socket)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                ObjectInputStream is = <span class="keyword">null</span>;</span><br><span class="line">                ObjectOutputStream os = <span class="keyword">null</span>;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    is = <span class="keyword">new</span> ObjectInputStream(<span class="keyword">new</span> BufferedInputStream(socket.getInputStream()));</span><br><span class="line">                    os = <span class="keyword">new</span> ObjectOutputStream(socket.getOutputStream());</span><br><span class="line"></span><br><span class="line">                    Object obj = is.readObject();</span><br><span class="line">                    User user = (User)obj;</span><br><span class="line">                    System.out.println(<span class="string">"user: "</span> + user.getName() + <span class="string">"/"</span> + user.getPassword());</span><br><span class="line"></span><br><span class="line">                    user.setName(user.getName() + <span class="string">"_new"</span>);</span><br><span class="line">                    user.setPassword(user.getPassword() + <span class="string">"_new"</span>);</span><br><span class="line"></span><br><span class="line">                    os.writeObject(user);</span><br><span class="line">                    os.flush();</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">                    logger.log(Level.SEVERE, <span class="keyword">null</span>, ex);</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        is.close();</span><br><span class="line">                        os.close();</span><br><span class="line">                        socket.close();</span><br><span class="line">                    &#125; <span class="keyword">catch</span>(Exception ex) &#123;&#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyClient</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> Logger logger = Logger.getLogger(MyClient.class.getName());</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">            Socket socket = <span class="keyword">null</span>;</span><br><span class="line">            ObjectOutputStream os = <span class="keyword">null</span>;</span><br><span class="line">            ObjectInputStream is = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                socket = <span class="keyword">new</span> Socket(<span class="string">"localhost"</span>, <span class="number">10000</span>);</span><br><span class="line"></span><br><span class="line">                os = <span class="keyword">new</span> ObjectOutputStream(socket.getOutputStream());</span><br><span class="line">                User user = <span class="keyword">new</span> User(<span class="string">"user_"</span> + i, <span class="string">"password_"</span> + i);</span><br><span class="line">                os.writeObject(user);</span><br><span class="line">                os.flush();</span><br><span class="line"></span><br><span class="line">                is = <span class="keyword">new</span> ObjectInputStream(<span class="keyword">new</span> BufferedInputStream(socket.getInputStream()));</span><br><span class="line">                Object obj = is.readObject();</span><br><span class="line">                <span class="keyword">if</span> (obj != <span class="keyword">null</span>) &#123;</span><br><span class="line">                    user = (User)obj;</span><br><span class="line">                    System.out.println(<span class="string">"user: "</span> + user.getName() + <span class="string">"/"</span> + user.getPassword());</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">catch</span>(IOException ex) &#123;</span><br><span class="line">                logger.log(Level.SEVERE, <span class="keyword">null</span>, ex);</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    is.close();</span><br><span class="line">                    os.close();</span><br><span class="line">                    socket.close();</span><br><span class="line">                &#125; <span class="keyword">catch</span>(Exception ex) &#123;&#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>　　在客户端和服务端的代码中，我们都用到了ObjectOutputStream和ObjectInputStream流以支持对象流的读写。</p>
<h2 id="u538B_u7F29_u4F20_u8F93"><a href="#u538B_u7F29_u4F20_u8F93" class="headerlink" title="压缩传输"></a>压缩传输</h2><p>　　总所周知，java的序列化会占用很大的空间，会导致一定的网络宽带压力。这里我们就直接介绍采用java.util.zip包提供的GZIPInputStream和GZIPOutputStream实现，关于thrift，protobuf的序列化方式将放到rpc系列中讨论。这里我们只列出关键的代码，如下所示。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">GZIPInputStream gzipis = <span class="keyword">new</span> GZIPInputStream(socket.getInputStream());</span><br><span class="line">ObjectInputStream ois = <span class="keyword">new</span> ObjectInputStream(gzipis);</span><br><span class="line">GZIPOutputStream gzipos = <span class="keyword">new</span> GZIPOutputStream(socket.getOutputStream());</span><br><span class="line">ObjectOutputStream oos = <span class="keyword">new</span> ObjectOutputStream(gzipos);</span><br></pre></td></tr></table></figure></p>
<h2 id="Socket_u53C2_u6570"><a href="#Socket_u53C2_u6570" class="headerlink" title="Socket参数"></a>Socket参数</h2><p>　　事实上，上面的例子中缺少了对socket参数的配置，这里我们直接贴出：<br>　　1. TCP_NODELAY: 表示立即发送数据。<br>　　2. SO_RESUSEADDR: 表示是否允许重用Socket 所绑定的本地地址。<br>　　3. SO_TIMEOUT: 表示接收数据时的等待超时时间，在java socket中，对应的方法为Socket的setSoTimeout方法。与Socket关联的InputStream的read方法被调用时，仅仅阻塞指定的时间，超时则抛出SocketTimeoutException（即使Socket连接仍然有效），这个方法必须在阻塞方法调用前调用才能生效，0表明无效超时。<br>　　4. SO_LINGER: 表示当执行Socket 的 close()方法时, 是否立即关闭底层的Socket。<br>　　5. SO_SNFBUF: 表示发送数据的缓冲区的大小。<br>　　6. SO_RCVBUF: 表示接收数据的缓冲区的大小。<br>　　7. SO_KEEPALIVE: 表示对于长时间处于空闲状态的Socket , 是否要自动把它关闭。<br>　　8. OOBINLINE: 表示是否支持发送一个字节的TCP 紧急数据。</p>
<p>示例代码：<a href="/code/socket-demo.zip">socket-demo.zip</a></p>
<h2 id="u53C2_u8003_u5730_u5740"><a href="#u53C2_u8003_u5730_u5740" class="headerlink" title="参考地址"></a>参考地址</h2><p><a href="http://blog.csdn.net/kongxx/article/details/7259465" target="_blank" rel="external">http://blog.csdn.net/kongxx/article/details/7259465</a><br><a href="http://blog.csdn.net/kongxx/article/details/7259827" target="_blank" rel="external">http://blog.csdn.net/kongxx/article/details/7259827</a><br><a href="http://blog.csdn.net/kongxx/article/details/7259834" target="_blank" rel="external">http://blog.csdn.net/kongxx/article/details/7259834</a></p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2015/12/23/nio/1.Socket入门/" data-id="cijzzpv83006i1cfssg1wmfzs" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/java/">java</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/socket/">socket</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-mongodb/14.mongodb系列之时序瓦片设计" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2015/12/18/mongodb/14.mongodb系列之时序瓦片设计/" class="article-date">
  <time datetime="2015-12-18T06:00:00.000Z" itemprop="datePublished">2015-12-18</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/mongodb/">mongodb</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2015/12/18/mongodb/14.mongodb系列之时序瓦片设计/">mongodb系列十四之时序瓦片设计</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="u5206_u6790"><a href="#u5206_u6790" class="headerlink" title="分析"></a>分析</h2><p>　　利用MongoDB对内嵌文档的支持。根据filename确定瓦片，使用data字段存储该瓦片的多时像数据，data字段为数组结构，如下图所示：<br><img src="/images/time_time.jpg" alt="time_time.jpeg"></p>
<h2 id="u67E5_u8BE2"><a href="#u67E5_u8BE2" class="headerlink" title="查询"></a>查询</h2><p>　　提取内嵌文档中的记录，如果存在多时相，可以返回数组中指定时像的tile，代码已测试通过。</p>
<p>　　查询内嵌文档中日期等于20120801的瓦片并返回，如果有多个，则返回多个瓦片，进一步添加limit操作，可以限定返回一个瓦片。</p>
<p>db.tilesTest.aggregate(<br>    {$match:{filename:”img_c_0_0_1”}},<br>    {$unwind:”$data”},<br>    {$match:{“data.t”:”20120801”}},<br>    {$group:{<br>        _id:”$_id”,<br>        filename:{$first:”$filename”},<br>        data:{$push:”$data”}}});<br>　　查询内嵌文档中日期小于20120801的瓦片并返回，如果有多个，则返回多个瓦片</p>
<p>db.tilesTest.aggregate(<br>    {$match:{filename:”img_c_0_0_1”}},<br>    {$unwind:”$data”},<br>    {$match:{“data.t”:{$lte:”20120801”}}},<br>    {$sort:”$data.t”},<br>    {$group:{_id:”$_id”,filename:{$first:”$filename”},data:{$push:”$data”}}});</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2015/12/18/mongodb/14.mongodb系列之时序瓦片设计/" data-id="cijzzpvap009m1cfszepq8v89" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/mongodb/">mongodb</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-mongodb/13.mongodb系列之限制" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2015/12/18/mongodb/13.mongodb系列之限制/" class="article-date">
  <time datetime="2015-12-18T05:00:00.000Z" itemprop="datePublished">2015-12-18</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/mongodb/">mongodb</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2015/12/18/mongodb/13.mongodb系列之限制/">mongodb系列十三之限制</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <ol>
<li>collection中 BSON文档的最大16M，超过该限制的文档请使用GridFS存储</li>
<li>文档嵌套不能超过100层</li>
<li>一个命名空间不能超过123bytes，包括db和collection，如ns.dbName.collectionName(建议验证一下)</li>
<li>一个collection或一个index为一个命名空间，Mongodb中默认命名空间文件为16M，最大指定为2047M。16M的命名空间文件最多存储的命名空间数为大约24000个命名空间。</li>
<li>被索引字段的值不能超过1KB，如果超过，该值则不会添加到索引中</li>
<li>每个集合的索引数不能超过64个</li>
<li>索引名长度不能超过125char(命名空间和集合名字计算在内)，建议在建立索引是指定名字。</li>
<li>组合索引（多字段构成的索引）内的字段不能超过31个</li>
<li>一次查询中不能同时使用全文检索和空间查询。</li>
<li>副本集不能超过12个，一个副本集中最多7个有选举权</li>
<li>在分片环境中不能使用的操作<br>1：group不可用，请使用mapReduce和aggregate替代<br>2：不能使用db.eval()；但可以在一个分片中非分片的collection中使用<br>3：$where中不容许包括db对象（很不常见）<br>4：$isolated的更新操作<br>5：$snapshot<br>6：geoSearch命令<br>7：covered queryis</li>
<li>对已存在的集合做分片，集合最大不超过256G，分片之后的集合可以任意大小</li>
<li>分片集合中，update()和remove()必须包括shard key或者是_id字段</li>
<li>在分片中不支持创建唯一索引，除非唯一索引的名称的前缀为完整的shard key（如果需要使用，请参考链接：<a href="http://docs.mongodb.org/manual/tutorial/enforce-unique-keys-for-sharded-collections/）" target="_blank" rel="external">http://docs.mongodb.org/manual/tutorial/enforce-unique-keys-for-sharded-collections/）</a></li>
<li>shard key不能超过512bytes</li>
<li>分片之后，shard key 不可改变，分片之前请考虑好shark key</li>
<li>作为shard key的字段的值在插入后不可改变</li>
<li>单调递增（减）的shard key会影响插入速度，默认的_id就是单调递增的<br>　　原因：When inserting documents with monotonically increasing shard keys, all inserts belong to the same chunk on a single shard. The system will eventually divide the chunk range that receives all write operations and migrate its contents to distribute data more evenly. However, at any moment the cluster can direct insert operations only to a single shard, which creates an insert throughput bottleneck.<br>　　If the operations on the cluster are predominately read operations and updates, this limitation may not affect the cluster.<br>　　To avoid this constraint, use a hashed shard key or select a field that does not increase or decrease monotonically.<br>　　Changed in version 2.4: Hashed shard keys and hashed indexes store hashes of keys with ascending values.<pre><code>如果更多的操作的读，更新，则该性能问题不存在
</code></pre></li>
<li>如果sort操作占用的空间少于32M，则直接返回无索引的字段，如果占用超过10%RAM，则报错</li>
<li>2d Geospatial queryis 不能使用$or操作</li>
<li>球面多边形必须适合在半球上（Spherical Polygons must fit within a hemisphere）<br>Any geometry specified with GeoJSON to $geoIntersects or $geoWithin queries, must fit within a single hemisphere. MongoDB interprets geometries larger than half of the sphere as queries for the smaller of the complementary geometries.</li>
<li>多个$in操作的限制。如果存在组合索引{a:1,b:1}，查询{ a: { $in: [ “a”, “b”, “c” ] }, b: { $in: [ “b”, “c” ] } }则不允许。如果查询结果查过400万，则出错”combinatorial limit of $in partitioning of result set exceeded”.</li>
<li>数据库名字大小写敏感，但是不允许存在名称中仅仅大小写不同的数据库，windows下数据库的名称不能包括/. “*&lt;&gt;:|?，也不能包括null character，linux下名称不能包括/. “，也不能包括null character</li>
<li>数据库名字需少于64个char</li>
<li>集合名字不能包括$、null character，不能是””，不能以system.开始</li>
<li>Field names cannot contain dots (i.e. .), dollar signs (i.e. $), or null characters</li>
</ol>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2015/12/18/mongodb/13.mongodb系列之限制/" data-id="cijzzpvat009p1cfsv9oje3sn" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/mongodb/">mongodb</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-mongodb/12.mongodb系列之性能监控" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2015/12/18/mongodb/12.mongodb系列之性能监控/" class="article-date">
  <time datetime="2015-12-18T04:00:00.000Z" itemprop="datePublished">2015-12-18</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/mongodb/">mongodb</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2015/12/18/mongodb/12.mongodb系列之性能监控/">mongodb系列十二之性能监控</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="mongosniff"><a href="#mongosniff" class="headerlink" title="mongosniff"></a>mongosniff</h2><p>　此工具可以从底层监控到底有哪些命令发送给了 MongoDB 去执行，从中就可以进行分析： 以 root 身份执行：./mongosniff –source NET lo 然后其会监控位到本地以 localhost 监听默认 27017 端口的 MongoDB 的所有包请求</p>
<h2 id="Mongostat"><a href="#Mongostat" class="headerlink" title="Mongostat"></a>Mongostat</h2><p>　　此工具可以快速的查看某组运行中的 MongoDB 实例的统计信息，用法：./mongostat。下面是执行结果(部分):</p>
<p>insert query update delete …… locked % idx miss % qr|qw ar|aw conn time<br>    <em>0 </em>0 <em>0 </em>0 …… 0 0 0|0 1|0 4 01:19:15<br>    <em>0 </em>0 <em>0 </em>0 …… 0 0 0|0 1|0 4 01:19:16<br>    <em>0 </em>0 <em>0 </em>0 …… 0 0 0|0 1|0 4 01:19:17<br>字段说明:<br>insert: 每秒插入量<br>query: 每秒查询量<br>update: 每秒更新量<br>delete: 每秒删除量<br>locked: 锁定量<br>qr | qw: 客户端查询排队长度(读|写)<br>ar | aw: 活跃客户端量(读|写)<br>conn: 连接数<br>time: 当前时间<br>它每秒钟刷新一次状态值，提供良好的可读性，通过这些参数可以观察到一个整体的性能情况。</p>
<h2 id="db-serverStatus"><a href="#db-serverStatus" class="headerlink" title="db.serverStatus"></a>db.serverStatus</h2><p>这个命令是最常用也是最基础的查看实例运行状态的命令之一，</p>
<h2 id="db-stats"><a href="#db-stats" class="headerlink" title="db.stats"></a>db.stats</h2><p>db.stats 查看数据库状态信息。使用样例如下:<br>{<br>        “db” : “test”,<br>        “collections” : 7, –collection 数量<br>        “objects” : 28, – 对象数量<br>        “avgObjSize” : 50.57142857142857, – 对象平均大小<br>        “dataSize” : 1416, – 数据大小<br>        “storageSize” : 31744, – 数据大小(含预分配空间)<br>        “numExtents” : 7, – 事件数量<br>        “indexes” : 7, –索引数量<br>        “indexSize” : 57344, – 索引大小<br>        “fileSize” : 50331648, –文件大小<br>        “ok” : 1 –本次取 stats 是否正常<br>} </p>
<h2 id="u7B2C_u4E09_u65B9_u5DE5_u5177"><a href="#u7B2C_u4E09_u65B9_u5DE5_u5177" class="headerlink" title="第三方工具"></a>第三方工具</h2><p>　　MongoDB 从一面世就得到众多开源爱好者和团队的重视，在常用的监控框架如 cacti 、 Nagios、Zabbix 等基础上进行扩展，进行 MongoDB 的监控都是非常方便的，有兴趣的朋友 可以自已去多试试。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2015/12/18/mongodb/12.mongodb系列之性能监控/" data-id="cijzzpvaw009s1cfst3e782bq" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/mongodb/">mongodb</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-mongodb/11.mongodb系列之优化" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2015/12/18/mongodb/11.mongodb系列之优化/" class="article-date">
  <time datetime="2015-12-18T03:00:00.000Z" itemprop="datePublished">2015-12-18</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/mongodb/">mongodb</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2015/12/18/mongodb/11.mongodb系列之优化/">mongodb系列十一之性能优化</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="capped_collection"><a href="#capped_collection" class="headerlink" title="capped collection"></a>capped collection</h2><p>　　capped Collections 比普通 Collections 的读写效率高。 Capped Collections 是高效率的 Collection类型，它有如下特点：</p>
<ol>
<li>固定大小；Capped Collections 必须事先创建，并设置大小：db.createCollection(“mycoll”, {capped:true, size:100000})</li>
<li>Capped Collections 可以 insert 和 update 操作；不能 delete 操作。只能用 drop（）方法删除整个 Collection。</li>
<li>默认基于 Insert 的次序排序的。如果查询时没有排序，则总是按照 insert 的顺序返回。</li>
<li>FIFO。如果超过了 Collection 的限定大小，则用 FIFO 算法，新记录将替代最先 insert 的 记录。</li>
<li>在 32 位机上，一个 capped collection 的最大值约为 482.5M，64 位上只受系统文件大小的限制。</li>
<li>MongoDB 中日志机制的首选，MongoDB 没有使用日志文件，而是把日志事件存储在数据库中。在一个没有索引的 capped collection 中插入对象的速度与在文件系统中记录日志的速度相当。 </li>
<li>可以利用 capped collection 的 age-out 特性，省去了写 cron 脚本进行人工归档的工作(auto archiving)。</li>
<li>为了发挥 capped collection 的最大性能，如果写比读多，最好不要在上面建索引，否则插入速度从”log speed”降为”database speed”。 </li>
<li>使用”nature ordering”可以有效地检索最近插入的元素，因为 capped collection 能够保证自然排序就是插入时的顺序，类似于 log 文件上的 tail 操作。</li>
<li>可以在创建 capped collection 时指定 collection 中能够存放的最大文档数。但这时也要指定 size，因为总是先检查 size 后检查maxRowNumber。可以使用 validate()查看一个 collection已经使用了多少空间，从而决定 size 设为多大。注意：max=1 时会往 collection 中存放尽量多的 documents。<br>db.createCollection(“mycoll”, {capped:true, size:100000, max:100});<br>db.mycoll.validate();</li>
<li>上述的 createCollection 函数也可以用来创建一般的 collection ，还有一个参数”autoIndexID”，值可以为”true”和”false”来决定是否需要在”_id”字段上自动创建索引，如：db.createCollection(“mycoll”, {size:10000000, autoIndexId:false})。默认情况下对一般的 collection 是创建索引的，但不会对 capped collection 创建。</li>
</ol>
<h2 id="Hint"><a href="#Hint" class="headerlink" title="Hint"></a>Hint</h2><p>　　一般情况下 MongoDB query optimizer 都工作良好，但有些情况下使用 hint()可以提高操作效 率。Hint可以强制要求查询操作使用某个索引。例如，如果要查询多个字段的值，如果在其中一个字段上有索引，可以使用 hint：db.collection.find({user:u, foo:d}).hint({user:1});</p>
<h2 id="explain"><a href="#explain" class="headerlink" title="explain"></a>explain</h2><p>　　MongoDB 提供了一个 explain（执行计划） 命令让我们获知系统如何处理查询请求。利用 explain 命令，我们可以很好地观察系统如何使用索引来加快检索，同时可以针对性优化索引。<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">&gt; db.t5.ensureIndex(&#123;name:<span class="number">1</span>&#125;)</span><br><span class="line">&gt; db.t5.ensureIndex(&#123;age:<span class="number">1</span>&#125;)</span><br><span class="line">&gt; db.t5.find(&#123;age:&#123;$gt:<span class="number">45</span>&#125;&#125;, &#123;name:<span class="number">1</span>&#125;).explain()</span><br><span class="line">&#123;</span><br><span class="line">        <span class="string">"cursor"</span> : <span class="string">"BtreeCursor age_1"</span>,</span><br><span class="line">        <span class="string">"nscanned"</span> : <span class="number">0</span>,</span><br><span class="line">        <span class="string">"nscannedObjects"</span> : <span class="number">0</span>,</span><br><span class="line">        <span class="string">"n"</span> : <span class="number">0</span>,</span><br><span class="line">        <span class="string">"millis"</span> : <span class="number">0</span>,</span><br><span class="line">        <span class="string">"nYields"</span> : <span class="number">0</span>,</span><br><span class="line">        <span class="string">"nChunkSkips"</span> : <span class="number">0</span>,</span><br><span class="line">        <span class="string">"isMultiKey"</span> : <span class="literal">false</span>,</span><br><span class="line">        <span class="string">"indexOnly"</span> : <span class="literal">false</span>,</span><br><span class="line">        <span class="string">"indexBounds"</span> : &#123;</span><br><span class="line">                <span class="string">"age"</span> : [</span><br><span class="line">                        [</span><br><span class="line">                                <span class="number">45</span>,</span><br><span class="line">                                <span class="number">1.7976931348623157</span>e +<span class="number">308</span></span><br><span class="line">                        ]</span><br><span class="line">                ] </span><br><span class="line">        &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>字段说明:</p>
<ol>
<li>cursor: 返回游标类型(BasicCursor 或 BtreeCursor)</li>
<li>nscanned: 被扫描的文档数量</li>
<li>n: 返回的文档数量</li>
<li>millis: 耗时(毫秒)</li>
<li>indexBounds: 所使用的索引</li>
</ol>
<h2 id="Profiling"><a href="#Profiling" class="headerlink" title="Profiling"></a>Profiling</h2><p>　　在 MySQL 中，慢查询日志是经常作为我们优化数据库的依据，那在 MongoDB 中是否有类似的功能呢?答案是肯定的，那就是 MongoDB Database Profiler。所以 MongoDB 不仅有，而且还有一些比 MySQL的 Slow Query Log 更详细的信息。<br>　　有两种方式可以控制 Profiling 的开关和级别，第一种是直接在启动参数里直接进行设置。 启动 MongoDB 时加上–profile=级别 即可。也可以在客户端调用 db.setProfilingLevel( 级别) 命令来实时配置，Profiler 信息保存在 system.profile（capped collection） 中。我们可以通过 db.getProfilingLevel()命令来获取当前的 Profile 级别，类似如下操作 </p>
<blockquote>
<p>db.setProfilingLevel(2);<br>{ “was” : 0, “slowms” : 100, “ok” : 1 }<br>　　上面 profile 的级别可以取 0，1，2 三个值，他们表示的意义如下：0 – 不开启   1 – 记录慢命令 (默认为&gt;100ms)  2 – 记录所有命令<br>　　Profile 记录在级别 1 时会记录慢命令，那么这个慢的定义是什么？上面我们说到其默认为100ms。当然有默认就有设置，其设置方法和级别一样有两种，一种是通过添加–slowms 启动参数配置。第二种是调用 db.setProfilingLevel 时加上第二个参数：<br>db.setProfilingLevel( level , slowms )<br>db.setProfilingLevel( 1 , 10 );<br>　　与 MySQL 的慢查询日志不同，MongoDB Profile 记录是直接存在系统 db 里的，记录位置 system.profile ，所以，我们只要查询这个Collection 的记录就可以获取到我们的 Profile 记录了。列出执行时间长于某一限度(5ms)的 Profile 记录：db.system.profile.find( { millis : { $gt : 5 } } ) 。现在我们查看最新的 Profile 记录：<br>db.system.profile.find().sort({$natural:-1}).limit(1)<br>db.system.profile.find().sort({$natural:-1}).limit(1)<br>{ “ts” : ISODate(“2012-05-20T16:50:36.321Z”),<br>  “info” : “query test.system.profile reslen:1219 nscanned:8 query: { query: {}, orderby: { $natural: -1.0 } } nreturned:8 bytes:1203”,<br>  “millis” : 0<br>}</p>
<p>　　字段说明:</p>
<ol>
<li>ts： 该命令在何时执行</li>
<li>info: 本命令的详细信息</li>
<li>reslen: 返回结果集的大小</li>
<li>nscanned: 本次查询扫描的记录数</li>
<li>nreturned: 本次查询实际返回的结果集</li>
<li>millis: 该命令执行耗时，以毫秒记</li>
</ol>
</blockquote>
<p>　　MongoDB Shell 还提供了一个比较简洁的命令 show profile，可列出最近 5 条执行时间超过1ms 的 Profile 记录。</p>
<h2 id="u67E5_u770B_u6D3B_u52A8_u8FDB_u7A0B"><a href="#u67E5_u770B_u6D3B_u52A8_u8FDB_u7A0B" class="headerlink" title="查看活动进程"></a>查看活动进程</h2><p>　　查看活动进程，便于了解系统正在做什么，以便做下一步判断<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&gt; db.currentOp();</span><br><span class="line">&gt; <span class="comment">// 等同于: db.$cmd.sys.inprog.findOne()</span></span><br><span class="line">&#123; inprog: [ &#123; <span class="string">"opid"</span> : <span class="number">18</span> , <span class="string">"op"</span> : <span class="string">"query"</span> , <span class="string">"ns"</span> :<span class="string">"mydb.votes"</span> ,</span><br><span class="line">              <span class="string">"query"</span> : <span class="string">"&#123; score : 1.0 &#125;"</span> , <span class="string">"inLock "</span> : <span class="number">1</span> &#125;</span><br><span class="line">          ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>字段说明:<br>Opid: 操作进程号<br>Op: 操作类型(查询，更新等)<br>Ns: 命名空间, 指操作的是哪个对象<br>Query: 如果操作类型是查询的话，这里将显示具体的查询内容<br>lockType: 锁的类型，指明是读锁还是写锁</p>
<h2 id="u7ED3_u675F_u8FDB_u7A0B"><a href="#u7ED3_u675F_u8FDB_u7A0B" class="headerlink" title="结束进程"></a>结束进程</h2><p>　　如果某个异常是由于某个进程产生的，那么一般 DBA 都会毫不留情的杀掉这个罪魁祸首的进程<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; db.killOp(<span class="number">1234</span><span class="comment">/*opid*/</span>)</span><br><span class="line">&gt; <span class="comment">// 等同于: db.$cmd.sys.killop.findOne(&#123;op:1234&#125;)</span></span><br></pre></td></tr></table></figure></p>
<p>　　注意:不要 kill 内部发起的操作，比如说 replica set 发起的 sync 操作等</p>
<h2 id="u4E3B_u4ECE_u64CD_u4F5C_u65E5_u5FD7"><a href="#u4E3B_u4ECE_u64CD_u4F5C_u65E5_u5FD7" class="headerlink" title="主从操作日志"></a>主从操作日志</h2><p>　　MongoDB 的 Replica Set 架构是通过一个日志来存储写操作的，这个日志就叫做”oplog”。 oplog.rs 是一个固定长度的 capped collection，它存在于”local”数据库中，用于记录 Replica Sets 操作日志。在默认情况下，对于 64 位的 MongoDB，oplog 是比较大的，可以达到 5%的磁 盘空间。oplog 的大小是可以通过 mongod 的参数”—oplogSize”来改变 oplog 的日志大小。该原理与Mysql类似。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2015/12/18/mongodb/11.mongodb系列之优化/" data-id="cijzzpvaz009v1cfsx8ezjhnw" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/mongodb/">mongodb</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-mongodb/10.mongodb系列之访问控制" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2015/12/18/mongodb/10.mongodb系列之访问控制/" class="article-date">
  <time datetime="2015-12-18T02:00:00.000Z" itemprop="datePublished">2015-12-18</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/mongodb/">mongodb</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2015/12/18/mongodb/10.mongodb系列之访问控制/">mongodb系列十之访问控制</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="u9650_u5236IP"><a href="#u9650_u5236IP" class="headerlink" title="限制IP"></a>限制IP</h2><p>　　MongoDB 可以限制只允许某一特定 IP 来访问，只要在启动时加一个参数 bind_ip 即可。如下:  服务端限制只有 192.168.1.103 这个 IP 可以访问 MongoDB 服务<br>./mongod –bind_ip 192.168.1.103</p>
<h2 id="u76D1_u542C_u7AEF_u53E3"><a href="#u76D1_u542C_u7AEF_u53E3" class="headerlink" title="监听端口"></a>监听端口</h2><p>　　官方默认的监听端口是 27017，为了安全起见，一般都会修改这个监听端口，避免恶意的连接尝试，将服务端监听端口修改为 28018，如下：<br>./mongod –bind_ip 192.168.1.103 –port 28018</p>
<h2 id="u7528_u6237_u540D_u548C_u5BC6_u7801"><a href="#u7528_u6237_u540D_u548C_u5BC6_u7801" class="headerlink" title="用户名和密码"></a>用户名和密码</h2><p>　　MongoDB默认的启动是不验证用户名和密码的，启动 MongoDB 后，可以直接用 MongoDB 连接上来，对所有的库具有 root 权限。所以启动的时候指定参数，可以阻止客户端的访问和连接。先启用系统的登录验证模块, 只需在启动时指定 auth 参数即可，如:<br>./mongod –auth </p>
<p>　　本地客户端连接一下看看效果，成功<br> ./mongo<br>　　很奇怪，为什么我们启用了登录验证模块，但我们登录时没有指定用户，为什么还可以登录呢？在最初始的时候 MongoDB 都默认有一个 admin 数据库（默认是空的），而 admin.system.users 中将会保存比在其它数据库中设置的用户权限更大的用户信息。注意：当 admin.system.users 中没有添加任何用户时，即使 MongoDB 启动时添加了 –auth<br>参数，如果在除 admin 数据库中添加了用户，此时不进行任何认证依然可以使用任何操作，直到知道你在 admin.system.users 中添加了一个用户。</p>
<p>　　</p>
<h2 id="u53EA_u8BFB_u7528_u6237"><a href="#u53EA_u8BFB_u7528_u6237" class="headerlink" title="只读用户"></a>只读用户</h2><p>　　MongoDB 支持为某个特定的数据库来设置用户，如我们为 test 库设一个只读的用户 user_reader<br>连接test数据库<br>执行db.addUser(“user_reader”, “user_pwd”, true);</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2015/12/18/mongodb/10.mongodb系列之访问控制/" data-id="cijzzpvb2009y1cfsnfko69ra" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/mongodb/">mongodb</a></li></ul>

    </footer>
  </div>
  
</article>


  
  
    <nav id="page-nav">
      <a class="extend prev" rel="prev" href="/page/7/">&laquo; Prev</a><a class="page-number" href="/">1</a><span class="space">&hellip;</span><a class="page-number" href="/page/6/">6</a><a class="page-number" href="/page/7/">7</a><span class="page-number current">8</span><a class="page-number" href="/page/9/">9</a><a class="page-number" href="/page/10/">10</a><span class="space">&hellip;</span><a class="page-number" href="/page/15/">15</a><a class="extend next" rel="next" href="/page/9/">Next &raquo;</a>
    </nav>
  
</section>
        
          <aside id="sidebar">
  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Categories</h3>
    <div class="widget">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/cache/">cache</a><span class="category-list-count">3</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/database/">database</a><span class="category-list-count">13</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/docker/">docker</a><span class="category-list-count">6</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/gis/">gis</a><span class="category-list-count">4</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/git/">git</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/go/">go</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/gradle/">gradle</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/groovy/">groovy</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/hadoop/">hadoop</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/java/">java</a><span class="category-list-count">18</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/linux/">linux</a><span class="category-list-count">4</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/log/">log</a><span class="category-list-count">11</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/mongodb/">mongodb</a><span class="category-list-count">14</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/mq/">mq</a><span class="category-list-count">6</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/mybatis/">mybatis</a><span class="category-list-count">9</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/netty/">netty</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/nginx/">nginx</a><span class="category-list-count">3</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/nio/">nio</a><span class="category-list-count">19</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/nodejs/">nodejs</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/redis/">redis</a><span class="category-list-count">6</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/rpc/">rpc</a><span class="category-list-count">14</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/spring/">spring</a><span class="category-list-count">5</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/tomcat/">tomcat</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/zookeeper/">zookeeper</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/杂项/">杂项</a><span class="category-list-count">2</span></li></ul>
    </div>
  </div>

  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Tags</h3>
    <div class="widget">
      <ul class="tag-list"><li class="tag-list-item"><a class="tag-list-link" href="/tags/aop/">aop</a><span class="tag-list-count">8</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/aspectj/">aspectj</a><span class="tag-list-count">3</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/cache/">cache</a><span class="tag-list-count">3</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/classloader/">classloader</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/common-loggings/">common-loggings</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/commonjs/">commonjs</a><span class="tag-list-count">4</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/database/">database</a><span class="tag-list-count">10</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/docker/">docker</a><span class="tag-list-count">6</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/dubbo/">dubbo</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/fastdfs/">fastdfs</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/gclib/">gclib</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/geohash/">geohash</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/gis/">gis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/git/">git</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/go/">go</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/gradle/">gradle</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/groovy/">groovy</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/hadoop/">hadoop</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/hessisan/">hessisan</a><span class="tag-list-count">3</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/java/">java</a><span class="tag-list-count">48</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/javassist/">javassist</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/kafka/">kafka</a><span class="tag-list-count">3</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/linux/">linux</a><span class="tag-list-count">4</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/log/">log</a><span class="tag-list-count">11</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/log4j/">log4j</a><span class="tag-list-count">4</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/logback/">logback</a><span class="tag-list-count">5</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/map/">map</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/memecached/">memecached</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/mina/">mina</a><span class="tag-list-count">5</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/mongodb/">mongodb</a><span class="tag-list-count">14</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/mq/">mq</a><span class="tag-list-count">6</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/mqtt/">mqtt</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/mybatis/">mybatis</a><span class="tag-list-count">9</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/netty/">netty</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/nginx/">nginx</a><span class="tag-list-count">3</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/nio/">nio</a><span class="tag-list-count">5</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/nodejs/">nodejs</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/postgresql/">postgresql</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/protobuf/">protobuf</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/proxy/">proxy</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/push/">push</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/rabbitmq/">rabbitmq</a><span class="tag-list-count">3</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/redis/">redis</a><span class="tag-list-count">6</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/rpc/">rpc</a><span class="tag-list-count">14</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/sitemesh/">sitemesh</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/sld/">sld</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/slf4j/">slf4j</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/socket/">socket</a><span class="tag-list-count">7</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/spatialite-for-android/">spatialite for android</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/spi/">spi</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/spring/">spring</a><span class="tag-list-count">13</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/tomcat/">tomcat</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/transaction/">transaction</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/web/">web</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/xSocket/">xSocket</a><span class="tag-list-count">7</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/zbus/">zbus</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/zookeeper/">zookeeper</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/数据结构/">数据结构</a><span class="tag-list-count">2</span></li></ul>
    </div>
  </div>

  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Tag Cloud</h3>
    <div class="widget tagcloud">
      <a href="/tags/aop/" style="font-size: 15.38px;">aop</a> <a href="/tags/aspectj/" style="font-size: 11.54px;">aspectj</a> <a href="/tags/cache/" style="font-size: 11.54px;">cache</a> <a href="/tags/classloader/" style="font-size: 10px;">classloader</a> <a href="/tags/common-loggings/" style="font-size: 10px;">common-loggings</a> <a href="/tags/commonjs/" style="font-size: 12.31px;">commonjs</a> <a href="/tags/database/" style="font-size: 16.92px;">database</a> <a href="/tags/docker/" style="font-size: 13.85px;">docker</a> <a href="/tags/dubbo/" style="font-size: 10px;">dubbo</a> <a href="/tags/fastdfs/" style="font-size: 10px;">fastdfs</a> <a href="/tags/gclib/" style="font-size: 10.77px;">gclib</a> <a href="/tags/geohash/" style="font-size: 10px;">geohash</a> <a href="/tags/gis/" style="font-size: 10px;">gis</a> <a href="/tags/git/" style="font-size: 10.77px;">git</a> <a href="/tags/go/" style="font-size: 10px;">go</a> <a href="/tags/gradle/" style="font-size: 10px;">gradle</a> <a href="/tags/groovy/" style="font-size: 10px;">groovy</a> <a href="/tags/hadoop/" style="font-size: 10px;">hadoop</a> <a href="/tags/hessisan/" style="font-size: 11.54px;">hessisan</a> <a href="/tags/java/" style="font-size: 20px;">java</a> <a href="/tags/javassist/" style="font-size: 10px;">javassist</a> <a href="/tags/kafka/" style="font-size: 11.54px;">kafka</a> <a href="/tags/linux/" style="font-size: 12.31px;">linux</a> <a href="/tags/log/" style="font-size: 17.69px;">log</a> <a href="/tags/log4j/" style="font-size: 12.31px;">log4j</a> <a href="/tags/logback/" style="font-size: 13.08px;">logback</a> <a href="/tags/map/" style="font-size: 10px;">map</a> <a href="/tags/memecached/" style="font-size: 10.77px;">memecached</a> <a href="/tags/mina/" style="font-size: 13.08px;">mina</a> <a href="/tags/mongodb/" style="font-size: 19.23px;">mongodb</a> <a href="/tags/mq/" style="font-size: 13.85px;">mq</a> <a href="/tags/mqtt/" style="font-size: 10.77px;">mqtt</a> <a href="/tags/mybatis/" style="font-size: 16.15px;">mybatis</a> <a href="/tags/netty/" style="font-size: 10px;">netty</a> <a href="/tags/nginx/" style="font-size: 11.54px;">nginx</a> <a href="/tags/nio/" style="font-size: 13.08px;">nio</a> <a href="/tags/nodejs/" style="font-size: 10px;">nodejs</a> <a href="/tags/postgresql/" style="font-size: 10.77px;">postgresql</a> <a href="/tags/protobuf/" style="font-size: 10px;">protobuf</a> <a href="/tags/proxy/" style="font-size: 10px;">proxy</a> <a href="/tags/push/" style="font-size: 10px;">push</a> <a href="/tags/rabbitmq/" style="font-size: 11.54px;">rabbitmq</a> <a href="/tags/redis/" style="font-size: 13.85px;">redis</a> <a href="/tags/rpc/" style="font-size: 19.23px;">rpc</a> <a href="/tags/sitemesh/" style="font-size: 10px;">sitemesh</a> <a href="/tags/sld/" style="font-size: 10px;">sld</a> <a href="/tags/slf4j/" style="font-size: 10px;">slf4j</a> <a href="/tags/socket/" style="font-size: 14.62px;">socket</a> <a href="/tags/spatialite-for-android/" style="font-size: 10px;">spatialite for android</a> <a href="/tags/spi/" style="font-size: 10px;">spi</a> <a href="/tags/spring/" style="font-size: 18.46px;">spring</a> <a href="/tags/tomcat/" style="font-size: 10.77px;">tomcat</a> <a href="/tags/transaction/" style="font-size: 10px;">transaction</a> <a href="/tags/web/" style="font-size: 10px;">web</a> <a href="/tags/xSocket/" style="font-size: 14.62px;">xSocket</a> <a href="/tags/zbus/" style="font-size: 10px;">zbus</a> <a href="/tags/zookeeper/" style="font-size: 10.77px;">zookeeper</a> <a href="/tags/数据结构/" style="font-size: 10.77px;">数据结构</a>
    </div>
  </div>

  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Archives</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/01/">January 2016</a><span class="archive-list-count">67</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/12/">December 2015</a><span class="archive-list-count">81</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/11/">November 2015</a><span class="archive-list-count">2</span></li></ul>
    </div>
  </div>

  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Recents</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/2016/01/30/资料/">(no title)</a>
          </li>
        
          <li>
            <a href="/2016/01/30/java/13.Classloader系列一/">ClassLoader系列一</a>
          </li>
        
          <li>
            <a href="/2016/01/29/hadoop/1.hadoop系列之源码编译/">hadoop系列一之源码编译</a>
          </li>
        
          <li>
            <a href="/2016/01/29/go/1.GO系列之入门/">Go系列一之入门</a>
          </li>
        
          <li>
            <a href="/2016/01/28/tomcat/2.tomcat性能优化/">tomcat性能优化</a>
          </li>
        
      </ul>
    </div>
  </div>

  
</aside>
        
      </div>
      <footer id="footer">
  
  <div class="outer">
    <div id="footer-info" class="inner">
      &copy; 2016 John Doe<br>
      Powered by <a href="http://hexo.io/" target="_blank">Hexo</a>
    </div>
  </div>
</footer>
    </div>
    <nav id="mobile-nav">
  
    <a href="/" class="mobile-nav-link">Home</a>
  
    <a href="/archives" class="mobile-nav-link">Archives</a>
  
</nav>
    


<script src="http://libs.baidu.com/jquery/2.0.3/jquery.min.js"></script>


  <link rel="stylesheet" href="/fancybox/jquery.fancybox.css" type="text/css">
  <script src="/fancybox/jquery.fancybox.pack.js" type="text/javascript"></script>


<script src="/js/script.js" type="text/javascript"></script>

  </div>
</body>
</html>